读者反馈了一个死锁案例,比较有意思,我分析总结了一篇文章。java
须要一些基础,下面是我在掘金上写的另外五篇调试源码分析锁的文章,能够顺便看看:数据库
下面开始真正的内容:spa
建表语句:3d
CREATE TABLE `tenant_config` (
`id` bigint(21) NOT NULL AUTO_INCREMENT,
`tenant_id` int(11) NOT NULL,
`open_card_point` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_tenant` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
复制代码
表中有一条初始化语句:指针
INSERT INTO `tenant_config` (`tenant_id`, `open_card_point`) VALUES (123,0);
复制代码
数据库隔离级别:RC
事务 1 和事务 2 语句一毛同样,都是下面这样:
INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;
复制代码
代码的逻辑大概以下,先插入,若是有冲突则更新
try {
insert();
} catch (DuplicateKeyException e) {
update()
}
复制代码
死锁条件的过程以下
事务 1:
INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);
ERROR 1062 (23000): Duplicate entry '123' for key 'uidx_tenant'
复制代码
加锁状况,对 uk 加 S 锁,以下:
事务 2:
INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);
ERROR 1062 (23000): Duplicate entry '123' for key 'uidx_tenant'
复制代码
加锁状况,对 uk 加 S 锁,以下:
事务 1:
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;
复制代码
对 uk 加 X 锁,由于事务 2 获取了 S 锁,进入锁等待
事务 2:
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;
复制代码
一样想对 uk 加 X 锁,死锁条件产生:事务 2 拿到了 S 锁,想加 X 锁,事务 1 拿到了 S 锁,也想加 X 锁,彼此都在等对方的 S 锁。
这种状况是最简单的,若是只是这么简单,我就不会写了,哈哈,下面来看第二种状况。
第一步:事务 1,插入惟一键冲突
begin;
INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);
ERROR 1062 (23000): Duplicate entry '123' for key 'uidx_tenant'
复制代码
第二步:事务 2
begin;
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;
复制代码
第三步:事务 1
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;
复制代码
出现:事务 2 死锁
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
复制代码
分析过程以下:
事务 1
INSERT INTO `tenant_config` ( `tenant_id`, `open_card_point`) VALUES (123,111111);
复制代码
对 uk 加 S 锁,这个没有什么歧义。
接下来事务 2
UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;
复制代码
这个对 ux 加 X 锁,进入锁等待状态,这个也没有什么问题。
接下来,事务 1 执行 update,状况就复杂不少了,也是想获取 X 锁,可是没有那么顺利。
进入死锁检测流程,重点代码在lock_deadlock_occurs()
函数,最近会进入 lock_deadlock_recursive()
递归调用函数。
死锁的本质是:在递归过程当中,若是冲突出现的锁事务id等于顶层事务id(lock_trx == start),则说明有环,就发生死锁。
如下记事务 1 为 t1,事务 2 为 t2
wait_lock 属于 t1 的 lock_X,就是 t1 update 想获取的 X 锁
这个时候会检查记录上全部的锁,第一个锁是 t1 事务的 S 锁,第二个锁是 t2 事务等待状态的 X 锁
检查第一把锁,t1 事务的 S 锁,由于与 wait_lock 属于同一个事务,没有冲突,继续检查第二把锁。
检查第二把锁,是 t2 事务处于等待状态的 X 锁,是互斥的,并且 t2 的 X 锁是处于等待状态的,开始第二次递归调用,检查 t2 的 X 锁,查看它在等待什么锁。
此时传入的 start 没变,wait_lock 变为了 t2 的 X 锁,也就是把 t2 的 X 锁拿出来检测,看跟现有锁有哪些依赖。
t2 的 X 锁在等待 t1 的 S 锁,lock_trx 等于 start,成环死锁产生。
也就是:t1 的 insert 插入加了 S 锁,t2 的 X 锁虽然没加成功,可是真实存在,标记为等待状态。t1 再想获取 X 锁,发现与 t2 等待状态的 X 锁冲突。再次检测,发现 t2 等待状态的 X 锁与 t1 的 S 锁冲突,死锁产生。
我画了一个图方便你理解:
死锁分析是比较复杂的,调试源码能够比较清晰的理清思路,上面是我调试源码的一些结论,若是有理解有误的地方,记得及时帮我指出。