这个案例的原文参见: mysql死锁场景汇总整理_死锁业务场景_秃了也弱了。的博客-CSDN博客
那么我们就来分析下整个加锁过程吧。
关键词: next-key & gap 锁 & 插入意向锁 & 二级普通索引
前提:RR级别
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`age` int(3) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `udx_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
insert into t_user(`age`) values (25);
insert into t_user(`age`) values (27);
insert into t_user(`age`) values (29);
insert into t_user(`age`) values (31);
执行流程和加锁分析见如下。
注
1)其中LOCK_ORDINARY 就是next key (也就是 rec + gap), 死锁日志中一般打印为 LOCK_MODE X,不打 lock ordinary 因为lock_ordinary 的mode值是0。
2) heap no +space id+ page id唯一定位一个索引的一个行记录。
每个page的 heap no = 0, 代表infimum,人为规定的最小值,1 代表supremum 人为规定的最大值。从2开始才是真正的记录。
session1 | session2 | 备注 |
start transaction; | start transaction; | |
delete from t_user where age =27; | 读二级索引上的匹配行 lock_sec_rec_read_check_and_lock uxd_age上, X, LOCK_ORDINARY, heap-no=2 pk上: x, rec_not_gap ,heap=2 lock_sec_rec_modify_check_and_lock 加锁 x,rec, udx_age, heap=2, X, REC_NOT_GAP,已加过,返回成功。 读二级索引上下一个行 lock_sec_rec_read_check_and_lock 又在uxd_age上加了gap锁,heap no =4 ,LOCK_GAP | |
do nothing | delete from t_user where age = 31; | 读二级索引上的匹配行 lock_sec_rec_read_check_and_lock heap no =5 ,uxd_age lock_x, lock ordiniary; heap_no = 5 , pk ,x, rec. lock_sec_rec_modify_check_and_lock uxd_age, heap = 5, x,rec, 已加过,返回成功 读二级索引下一个匹配行 uxd_age ,heap=1(supremum), x, lock ordinary. |
insert into t_user(`age`) values (30); | do nothing | session1执行结果阻塞 lock_rec_insert_check_and_lock add_to_waitq insert 待申请的锁: LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION, heap no = 5, index= udx_age, type mode 35(loc x lock_ordinary) |
do nothing | insert into t_user(`age`) values (28); | session2执行结果报deadlock lock_rec_insert_check_and_lock add_to_waitq insert 待申请的锁: LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION, heap no = 4, index= udx_age. 等待的锁: type mode = 547, index udx_age, heap no = 4. T2 ROLLBACK. 计算出死锁 |