innodb next-key lock引发的死锁
innodb的事务隔离级别是可重复读级别且innodb_locks_unsafe_for_binlog禁用,也就是说允许next-key
lock
CREATE TABLE `LockTest` (
`order_id` varchar(20) NOT NULL,
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8
事务1 | 事务2 |
begin delete from LockTest where order_id = ‘D20‘ |
|
begin delete from LockTest where order_id = ‘D19‘ | |
insert into LockTest (order_id) values (‘D20‘) |
|
insert into LockTest (order_id) values (‘D19‘) | |
commit |
commit |
事务1 执行到insert语句会block住,事务2执行insert语句会提示死锁错误
错误码: 1213
Deadlock found when trying to get lock; try restarting
transaction
Execution Time : 00:00:00:000
Transfer Time : 00:00:00:000
Total Time
: 00:00:00:000
show engine innodb status 显示死锁信息
------------------------
LATEST DETECTED
DEADLOCK
------------------------
2014-04-30 15:01:55 a233b90
*** (1)
TRANSACTION:
TRANSACTION 596042, ACTIVE 7 sec inserting
mysql tables in
use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 320, 2 row lock(s),
undo log entries 1
MySQL thread id 10851, OS thread handle 0x2abfb90, query
id 251521 10.10.53.122 root update
insert into LockTest (order_id) values
(‘D20‘)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id
502 page no 4 n bits 72 index `idx_order_id` of table `test`.`LockTest` trx id
596042 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL
RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex
73757072656d756d; asc supremum;;
*** (2) TRANSACTION:
TRANSACTION 596041, ACTIVE 19 sec inserting
mysql
tables in use 1, locked 1
3 lock struct(s), heap size 320, 2 row lock(s),
undo log entries 1
MySQL thread id 10848, OS thread handle 0xa233b90, query
id 251522 10.10.53.122 root update
insert into LockTest (order_id) values
(‘D19‘)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 502 page no 4 n
bits 72 index `idx_order_id` of table `test`.`LockTest` trx id 596041 lock_mode
X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info
bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 502
page no 4 n bits 72 index `idx_order_id` of table `test`.`LockTest` trx id
596041 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL
RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex
73757072656d756d; asc supremum;;
*** WE ROLL BACK TRANSACTION (2)
简单分析上面的场景先删除再插入的sql是hibernage保存集合关联的处理方式。delete语句删除不存在且删除的order_id大于现有表中的所有order_id,所以delete语句会使用next-key锁住(当前最大-无穷大)
lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
596133:502:4:1 | 596133 | X | RECORD | `test`.`LockTest` | idx_order_id | 502 | 4 | 1 | supremum pseudo-record |
596134:502:4:1 | 596134 | X | RECORD | `test`.`LockTest` | idx_order_id | 502 | 4 | 1 | supremum pseudo-record |
比较奇怪的是为啥两个事务都拿到了相同区间的(当前最大-无穷大)的X锁。不过换成read-commited级别后就没死锁了。
终于在官方文档找到答案, 区间锁只是用来防止其他事务在区间中插入数据,区间x锁 与区间S锁效果是一样的。也就是说不会因为两个事务都用加相同区间锁而相互等待的
https://dev.mysql.com/doc/refman/5.1/en/innodb-record-level-locks.html
Gap locks in InnoDB
are “purely inhibitive”, which
means they only stop other transactions from inserting to the gap. Thus, a gap
X-lock has the same effect as a gap S-lock.
当两个事务拿到相同区间锁后,就会阻止对方忘区间内做insert操作。所以第一个事务insert会阻塞,第二个事务会提示死锁