MySQL并发死锁案例分析一
目录
2、数据库准备
3、案例分析
CREATE TABLE `student` (
`ID` varchar(100) NOT NULL,
`NAME` varchar(100) DEFAULT NULL,
`AGE` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into test.student (ID, NAME, AGE) values(1,'张','11');
insert into test.student (ID, NAME, AGE) values(2,'李','22');
insert into test.student (ID, NAME, AGE) values(3,'刘','33');
mysql> select @ ;
+-----------------+
| @ |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
select * from information_schema.innodb_locks;
select * from information_schema.innodb_lock_waits;
select * from information_schema.innodb_trx;
show engine innodb status;
二、案例分析
执行T2的delete语句的时候会发现T2被阻塞住了,这是因为T2同样和T1一样同样要申请IS锁,S锁和X、Gap锁,读锁用于查看是否存在age为11的记录,因为T1的锁和T2的X锁是不兼容的,因此T2的S锁就被T1的X锁阻塞了,之所以说T2在等待获取X锁,是因为这个时候T2的读锁和间隙锁都是和T1的锁之间是兼容的,但是到T2申请X锁的时候才被阻塞住。
我们来看下执行第二条delete时INNODB_TRX表中已经有了二条记录表示T1、T2的事务信息。
select * from information_schema.innodb_locks;
select * from information_schema.innodb_lock_waits;
------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-08-08 15:36:29 0x70001046b000
*** (1) TRANSACTION:
TRANSACTION 2771286, ACTIVE 11 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 3, OS thread handle 123145575657472, query id 868 localhost root updating
delete from student where age = 11
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 76 page no 4 n bits 72 index age_index of table `test`.`student` trx id 2771286 lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 8000000b; asc ;;
1: len 1; hex 31; asc 1;;
*** (2) TRANSACTION:
TRANSACTION 2771285, ACTIVE 16 sec inserting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 2, OS thread handle 123145575378944, query id 869 localhost root update
insert into test.student (ID, NAME, AGE) values(4,'张','10')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 76 page no 4 n bits 72 index age_index of table `test`.`student` trx id 2771285 lock_mode X
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 8000000b; asc ;;
1: len 1; hex 31; asc 1;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 76 page no 4 n bits 72 index age_index of table `test`.`student` trx id 2771285 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 8000000b; asc ;;
1: len 1; hex 31; asc 1;;
*** WE ROLL BACK TRANSACTION (1)
这里的TRANSACTION1其实是实例中的T2,TRANSACTION2实际上是示例中的T1,重点看T1和T2目前持有什么锁,需要加什么锁,首先看T2:
RECORD LOCKS space id 76 page no 4 n bits 72 index age_index of table `test`.`student` trx id 2771286 lock_mode X waiting
T2正在申请lock_mode X ,处于waiting状态。
然后看T1:
RECORD LOCKS space id 76 page no 4 n bits 72 index age_index of table `test`.`student` trx id 2771285 lock_mode X
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 8000000b; asc;;
1: len 1; hex 31; asc 1;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 76 page no 4 n bits 72 index age_index of table `test`.`student` trx id 2771285 lock_mode X locks gap before rec insert intention waiting
T1目前已经持有了X间隙互斥锁,正在等待lock_mode X locks gap before rec insert intention间隙插入意向锁,也就是IIGap锁。
T2等待T1的X锁释放,T1等待T2的S锁释放,所以形成了循环依赖,由于T2的undo日志量较小,所以回滚T2,执行T1的SQL。
场景②、
下面将上个例子进行小小的变形,如下:
执行SQL及最终执行结果如下:
并没有出现死锁现象,最终数据库数据如下:
age为11的数据最终被T2删除。
没有发生死锁这就说明T1在最终执行insert语句的时候没有被T2阻塞住,但是T2确实是在执行delete的时候被T1阻塞住了,这个在执行的过程中会看见,这里只是最终结果,具体流程细节可自行尝试,那就说明T2在进行delete语句的时候获取的是间隙锁(-∞,11),而非Next-key锁,要不然T1在执行insert语句的时候一定会等待T2释放age为11的行锁,当然,如果T1最终插入数据age只要小于11,都会发生死锁。
当然,对于T2或者T1的update道理类似,执行SQL流程如下,死锁原因和之前一样。
以上事例造成死锁的原因大致可以归纳为间隙锁造成的死锁案例,如果明白SQL执行加锁顺序和各种锁之间的兼容性,分析死锁原因应该就很简单了。