mysql insert on duplicate_一条Insert on duplicate引发的血案
今天濤哥跟我說mysql死鎖了,問我怎么回事,我對于mysql的自我感覺一直很良好,覺得不會有啥大問題,結果真的把我難住了。
表
CREATE TABLE `test_dup` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`num` int(10) unsigned DEFAULT NULL,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `num_index` (`num`)
) ENGINE=InnoDB AUTO_INCREMENT=138 DEFAULT CHARSET=utf8;
已有數據
11hi
22dcdcdf
303dcdcdf2
335043494
3420dcdcdf473894739
58200dcdcdf
mysql的版本和隔離級別
Repeatable read
mysql 5.7.17.
濤哥的操作
多個線程,每個線程執行許多這樣的操作,但是保證每個線程執行的num是絕對不一樣的:
insert into test_dup(num,name) values(38,'no') on duplicate key update name='dcdcdf4738';
事務大小設置為100。
死鎖日志
LATEST DETECTED DEADLOCK
------------------------
2017-09-14 22:35:44 0x7f8f447c6700
*** (1) TRANSACTION:
TRANSACTION 6559008, ACTIVE 7 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 67521, OS thread handle 140253305255680, query id 16260084 10.47.54.38 sync_master update
insert into test_dup(num,name) values(39,'no') on duplicate key update name='dcdcdf4738'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2392 page no 4 n bits 80 index num_index of table `idoo`.`test_dup` trx id 6559008 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 10 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 00000028; asc (;;
1: len 4; hex 8000008a; asc ;;
*** (2) TRANSACTION:
TRANSACTION 6558977, ACTIVE 21 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 67531, OS thread handle 140253306054400, query id 16260339 10.47.54.38 sync_master update
insert into test_dup(num,name) values(38,'no') on duplicate key update name='dcdcdf4738'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 2392 page no 4 n bits 80 index num_index of table `idoo`.`test_dup` trx id 6558977 lock_mode X locks gap before rec
Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 00000032; asc 2;;
1: len 4; hex 80000021; asc !;;
Record lock, heap no 10 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 00000028; asc (;;
1: len 4; hex 8000008a; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2392 page no 4 n bits 80 index num_index of table `idoo`.`test_dup` trx id 6558977 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 10 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 00000028; asc (;;
1: len 4; hex 8000008a; asc ;;
從日志上來看似乎是兩個事務各自在等待對方的gap lock。
死鎖重現第一個事務執行
insert into test_dup(num,name) values(41,'no') on duplicate key update name='dcdcdf4738';
成功
第二個事務執行
insert into test_dup(num,name) values(40,'no') on duplicate key update name='dcdcdf4738';
事務陷入等待
第一個事務執行
insert into test_dup(num,name) values(38,'no') on duplicate key update name='dcdcdf4738';
顯示死鎖。
mysql可以打開鎖統計,通過以下語句打開mysql的鎖的統計
set GLOBAL innodb_status_output_locks=ON;
set GLOBAL innodb_status_output=ON;
在第二步的時候我們運行show engine innodb status;查看鎖的情況發現:
事務1持有:IX鎖(表鎖),gap x鎖(在num_index上num=50之前的gap),gap x鎖(在num_index上num=41之前的gap),num_index上的record lock(num=41),
事務2持有:IX鎖(表鎖),gap x鎖(在num_index上num=41之前的gap),insert intention lock(在等待事務1的第二個gap鎖)。
如果這時候第三步執行,那么事務1的insert intention也會等待事務2的gap鎖,死鎖形成。
原因分析第二個事務在獲取insert intention lock之前先獲取了gap lock,導致第一個事務也不能獲取insert intention lock。gap lock不是被第一個事務獲取了么?是互斥鎖呀?因為mysql的gap鎖是兼容的,與互斥還是只讀無關。所以第二個gap鎖才能獲取。
為什么insert intention lock的獲取在gap lock獲取之后,導致悲劇的發生。如果intention insert lock在之前那么就不會有死鎖了。這可能是mysql5.7的一個bug吧.
解決方案單線程執行數據庫寫入
減小事務的大小
修改事務隔離級別為read committed,read committed隔離級別鎖的粒度是index lock。只有在foreign-key constraint checking 和duplicate-key checking.的時候才會使用gap lock。
這里什么叫duplicate-key checking.的時候才會使用gap lock?舉個例子,update test_dup set num=1 where num=50;會引起Duplicate entry '1' for key 'num_index'。此時查看此事務的加鎖。index lock(num=50),index lock(num=1), s next key lock(num=1)。此時執行一個插入num=0的事務一定會阻塞。
不要使用 insert on duplicate,使用普通的insert。
insert會在num_index和pk中加record x locks,而不是gap lock或者next key lock,所以不會有死鎖。
盡量減少在數據庫中使用unique index和foreign key
因為unique key 和foreign key會引起額外的index檢查,需要更大的開銷。
mysql版本改為5.6
這里要特別說一下,mysql5.6是沒有這個問題的,insert into test_dup(num,name) values(41,'no') on duplicate key update name='dcdcdf4738'; 會在最終的num的index上加index lock(num=41),所以不會有死鎖。
思考
為什么mysql對于5.6和5.7中的insert on duplicate的加鎖處理會變化如此大?其實如此大的變更我們可以直接去看mysql的release note。在5.7的release note中我們發現了這樣一條。
INSERT INTO ... SELECT ... ON DUPLICATE KEY UPDATE and LOAD DATA CONCURRENT REPLACE took too weak a lock, leading to the possibility of concurrent SELECT statements returning inconsistent results. (Bug #38046, Bug #11749055)
大意就是原來加鎖太弱了,會引起RR隔離級別下的數據不一致,所以加強了。好吧。
總結
以上是生活随笔為你收集整理的mysql insert on duplicate_一条Insert on duplicate引发的血案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【图像压缩】基于matlab余弦变换及霍
- 下一篇: IP地址和 MAC地址详解