1、环境说明
MySQL5.6.33,隔离级别是RR。表结构及数据:
create table t2 (
id int primary key auto_increment ,
c1 int ,
c2 int ,
key (c1)
) engine=innodb ;
insert into t2 values(24,3,4),(25,3,4),(26,3,4),(30,5,8);
2、测试用例
session1 | session2 |
begin; | begin; |
select *from t2 where id=30 for update; | |
| update t2 set c2=8 where c1=5; |
delete from t2 where id=30; | |
| deadlock |
3、死锁日志
LATEST DETECTED DEADLOCK
------------------------
2018-07-14 06:45:32 a3522b90
*** (1) TRANSACTION:
TRANSACTION 8976, ACTIVE 104 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 320, 2 row lock(s)
MySQL thread id 2, OS thread handle 0xa34f1b90, query id 28 localhost root Searching rows for update
update t2 set c2=8 where c1=5
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 19 page no 3 n bits 72 index `PRIMARY` of table `yzs`.`t2` trx id 8976 lock_mode X locks rec but not gap waiting
Record lock, heap no 5 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
0: len 4; hex 8000001e; asc ;;
1: len 6; hex 00000000230f; asc # ;;
2: len 7; hex 0d000001410943; asc A C;;
3: len 4; hex 80000005; asc ;;
4: len 4; hex 80000008; asc ;;
*** (2) TRANSACTION:
TRANSACTION 8975, ACTIVE 251 sec updating or deleting
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 1, OS thread handle 0xa3522b90, query id 29 localhost root updating
delete from t2 where id=30
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 19 page no 3 n bits 72 index `PRIMARY` of table `yzs`.`t2` trx id 8975 lock_mode X locks rec but not gap
Record lock, heap no 5 PHYSICAL RECORD: n_fields 5; compact format; info bits 32
0: len 4; hex 8000001e; asc ;;
1: len 6; hex 00000000230f; asc # ;;
2: len 7; hex 0d000001410943; asc A C;;
3: len 4; hex 80000005; asc ;;
4: len 4; hex 80000008; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 19 page no 4 n bits 72 index `c1` of table `yzs`.`t2` trx id 8975 lock_mode X locks rec but not gap waiting
Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 4; hex 80000005; asc ;;
1: len 4; hex 8000001e; asc ;;
*** WE ROLL BACK TRANSACTION (1)
4、分析死锁日志
TRANSACTION 8975:
执行完delete from t2 where id=30后,拥有的锁是聚集索引记录(30,5,8)的X类型的记录锁lock_mode X locks rec but not gap,申请等待的锁是二级索引记录(5,30)的X类型的记录锁lock_mode X locks rec but not gap waiting。
TRANSACTION 8976:
执行完update t2 set c2=8 where c1=5语句时,显示申请等待的锁是聚集索引记录(30,5,8)的X类型的记录锁lock_mode X locks rec but not gap waiting。
只从死锁日志上看不出造成死锁的原因。未提交的事务中,有已执行过但未提交的SQL语句锁造成拥有的锁没有显示出来。
5、加锁原理
6、解析
session1执行完select *from t2 where id=30 for update;后,搜索到主键30,即对(30,5,8)加上X类型的记录锁
session2执行完update t2 set c2=8 where c1=5;后,在search阶段先搜索二级索引,搜索到(5,30),对此记录加X类型的next-key锁 lock_mode X;然后搜索聚集索引记录30,需要对聚集索引记录(30,5,8)加X类型记录锁,因为和session1互斥,所以锁等待。
session1再次执行delete from t2 where id=30;,对记录锁聚集索引记录(30,5,8)申请X类型的记录锁,因为事务前面已申请过,所以不再申请;再对二级索引记录(5,30)加X类型的记录锁,和session2互斥。
这样彼此发生锁等待,造成死锁。
7、解决方法
杨奇龙兄建议可以将session2的update t2 set c2=8 where c1=5改成按主键更新 update tx set c2=x where id=30。这样加锁顺序都是先聚集再二级索引,避免主键和二级索引加锁顺序交叉造成死锁。
8、参考
http://blog.itpub.net/22664653/viewspace-2152274/