mysql Insert on duplicate引发的死锁
表
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;
已有數(shù)據(jù)
1 1 hi
2 2 dcdcdf
30 3 dcdcdf2
33 50 43494
34 20 dcdcdf473894739
58 200 dcdcdf
?
mysql的版本和隔離級別
Repeatable read
mysql 5.7.17.
濤哥的操作
多個線程,每個線程執(zhí)行許多這樣的操作,但是保證每個線程執(zhí)行的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。
死鎖重現(xiàn)
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可以打開鎖統(tǒng)計,通過以下語句打開mysql的鎖的統(tǒng)計
set GLOBAL innodb_status_output_locks=ON;
set GLOBAL innodb_status_output=ON;
在第二步的時候我們運行show engine innodb status;查看鎖的情況發(fā)現(xiàn):
事務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鎖)。
如果這時候第三步執(zhí)行,那么事務1的insert intention也會等待事務2的gap鎖,死鎖形成。
原因分析
?
解決方案
- 單線程執(zhí)行數(shù)據(jù)庫寫入
- 減小事務的大小
- 修改事務隔離級別為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)。此時執(zhí)行一個插入num=0的事務一定會阻塞。
- 不要使用 insert on duplicate,使用普通的insert。
insert會在num_index和pk中加record x locks,而不是gap lock或者next key lock,所以不會有死鎖。 - 盡量減少在數(shù)據(jù)庫中使用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中我們發(fā)現(xiàn)了這樣一條。
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隔離級別下的數(shù)據(jù)不一致,所以加強了。好吧。
總結(jié)
以上是生活随笔為你收集整理的mysql Insert on duplicate引发的死锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java的ClassLoader
- 下一篇: MySQL锁总结