前言
如果我们的业务处在一个非常初级的阶段,并发程度比较低,那么我们可以几年都遇不到一次死锁问题的发生,反之,我们业务的并发程度非常高,那么时不时爆出的死锁问题肯定让我们非常挠头。不过在死锁问题发生时,很多没有经验的同学的第一反应就是成为一只鸵鸟:这玩意儿很高深,我也看不懂,听天由命吧,又不是一直发生。其实如果大家认真研读了我们之前写的3篇关于MySQL中语句加锁分析的文章,加上本篇关于死锁日志的分析,那么解决死锁问题应该也不是那么摸不着头脑的事情了。
准备工作
为了故事的顺利发展,我们需要建一个表:
CREATE TABLE hero ( id INT, name VARCHAR(100), country varchar(100), PRIMARY KEY (id), KEY idx_name (name) ) Engine=InnoDB CHARSET=utf8;
我们为hero表的id列创建了聚簇索引,为name列创建了一个二级索引。这个hero表主要是为了存储三国时的一些英雄,我们向表中插入一些记录:
INSERT INTO hero VALUES (1, 'l刘备', '蜀'), (3, 'z诸葛亮', '蜀'), (8, 'c曹操', '魏'), (15, 'x荀彧', '魏'), (20, 's孙权', '吴');
现在表中的数据就是这样的:
mysql> SELECT * FROM hero; +----+------------+---------+ | id | name | country | +----+------------+---------+ | 1 | l刘备 | 蜀 | | 3 | z诸葛亮 | 蜀 | | 8 | c曹操 | 魏 | | 15 | x荀彧 | 魏 | | 20 | s孙权 | 吴 | +----+------------+---------+ 5 rows in set (0.00 sec)
准备工作就做完了。
创建死锁情景
我们先创建一个发生死锁的情景,在Session A和Session B中分别执行两个事务,具体情况如下:
我们分析一下:
从第③步中可以看出,Session A中的事务先对hero表聚簇索引的id值为1的记录加了一个X型正经记录锁。 从第④步中可以看出,Session B中的事务对hero表聚簇索引的id值为3的记录加了一个X型正经记录锁。 从第⑤步中可以看出,Session A中的事务接着想对hero表聚簇索引的id值为3的记录也加了一个X型正经记录锁,但是与第④步中Session B中的事务加的锁冲突,所以Session A进入阻塞状态,等待获取锁。 从第⑥步中可以看出,Session B中的事务想对hero表聚簇索引的id值为1的记录加了一个X型正经记录锁,但是与第③步中Session A中的事务加的锁冲突,而此时Session A和Session B中的事务循环等待对方持有的锁,死锁发生,被MySQL服务器的死锁检测机制检测到了,所以选择了一个事务进行回滚,并向客户端发送一条消息:ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
以上是我们从语句加了什么锁的角度出发来进行死锁情况分析的,但是实际应用中我们可能压根儿不知道到底是哪几条语句产生了死锁,我们需要根据MySQL在死锁发生时产生的死锁日志来逆向定位一下到底是什么语句产生了死锁,从而再优化我们的业务。
查看死锁日志
设计InnoDB的大叔给我们提供了SHOW ENGINE INNODB STATUS命令来查看关于InnoDB存储引擎的一些状态信息,其中就包括了系统最近一次发生死锁时的加锁情况。在上边例子中的死锁发生时,我们运行一下这个命令:
mysql> SHOW ENGINE INNODB STATUS\G ...省略了好多其他信息 ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2019-06-20 13:39:19 0x70000697e000 *** (1) TRANSACTION: TRANSACTION 30477, ACTIVE 10 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1160, 2 row lock(s) MySQL thread id 2, OS thread handle 123145412648960, query id 46 localhost 127.0.0.1 root statistics select * from hero where id = 3 for update *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 171 page no 3 n bits 72 index PRIMARY of table `dahaizi`.`hero` trx id 30477 lock_mode X locks rec but not gap waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000003; asc ;; 1: len 6; hex 000000007517; asc u ;; 2: len 7; hex 80000001d0011d; asc ;; 3: len 10; hex 7ae8afb8e8919be4baae; asc z ;; 4: len 3; hex e89c80; asc ;; *** (2) TRANSACTION: TRANSACTION 30478, ACTIVE 8 sec starting index read mysql tables in use 1, locked 1 3 lock struct(s), heap size 1160, 2 row lock(s) MySQL thread id 3, OS thread handle 123145412927488, query id 47 localhost 127.0.0.1 root statistics select * from hero where id = 1 for update *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 171 page no 3 n bits 72 index PRIMARY of table `dahaizi`.`hero` trx id 30478 lock_mode X locks rec but not gap Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000003; asc ;; 1: len 6; hex 000000007517; asc u ;; 2: len 7; hex 80000001d0011d; asc ;; 3: len 10; hex 7ae8afb8e8919be4baae; asc z ;; 4: len 3; hex e89c80; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 171 page no 3 n bits 72 index PRIMARY of table `dahaizi`.`hero` trx id 30478 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 6; hex 000000007517; asc u ;; 2: len 7; hex 80000001d00110; asc ;; 3: len 7; hex 6ce58898e5a487; asc l ;; 4: len 3; hex e89c80; asc ;; *** WE ROLL BACK TRANSACTION (2) ------------ ...省略了好多其他信息