mysql 播入和更新死锁_大招落地:MySQL 插入更新死锁源码分析

今天再来分析一个死锁场景。下面开始真正的内容。

建表语句:

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

两条 insert,两条 update

事务 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 锁,如下:

ae408589562d3cfea5716f01eea691a5.png

事务 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 锁,如下:

1206c7448e027177a95c234e342675b3.png

事务 1:

UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;

对 uk 加 X 锁,因为事务 2 获取了 S 锁,进入锁等待

bb742c93bfc887edaa42b3d06dc394d8.png

事务 2:

UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123;

同样想对 uk 加 X 锁,死锁条件产生:事务 2 拿到了 S 锁,想加 X 锁,事务 1 拿到了 S 锁,也想加 X 锁,彼此都在等对方的 S 锁。

dd92865a9df81ea52dada9499a764fd0.png

这种情况是最简单的,如果只是这么简单,我就不会写了,哈哈,下面来看第二种情况。

一条 insert,两条 update

第一步:事务 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 锁,这个没有什么歧义。

ae408589562d3cfea5716f01eea691a5.png

接下来事务 2

UPDATE tenant_config SET open_card_point = 0 where tenant_id = 123 and 1 =1;

这个对 ux 加 X 锁,进入锁等待状态,这个也没有什么问题。

d50cb95e168c6e5f699ccac88a43e999.png

接下来,事务 1 执行 update,情况就复杂很多了,也是想获取 X 锁,但是没有那么顺利。

进入死锁检测流程,重点代码在lock_deadlock_occurs()函数,最近会进入 lock_deadlock_recursive()递归调用函数。

88c3c301f8e41cbea4d37a4c527c18c2.png

start 表示顶层调用该函数的事务指针,比如现在正在执行的事务 1 就是 start

wait_lock 表示想要获取的锁,这里是事务 1 对 uk 的 X 锁。

trx 等待锁的事务指针

死锁的本质是:在递归过程中,如果冲突出现的锁事务id等于顶层事务id(lock_trx == start),则说明有环,就发生死锁。

390175f4f6248f261015e0b7538310f9.png

以下记事务 1 为 t1,事务 2 为 t2

第一次递归

wait_lock 属于 t1 的 lock_X,就是 t1 update 想获取的 X 锁

133b15071b3f4e491f174bb814467cd5.png

这个时候会检查记录上所有的锁,第一个锁是 t1 事务的 S 锁,第二个锁是 t2 事务等待状态的 X 锁

检查第一把锁,t1 事务的 S 锁,因为与 wait_lock 属于同一个事务,没有冲突,继续检查第二把锁。

0ede310bcb7f4e5255f89c64ac27a563.png

检查第二把锁,是 t2 事务处于等待状态的 X 锁,是互斥的,而且 t2 的 X 锁是处于等待状态的,开始第二次递归调用,检查 t2 的 X 锁,查看它在等待什么锁。

0485b2dfd107750d63faaa7ba185b47e.png

第二次递归

此时传入的 start 没变,wait_lock 变为了 t2 的 X 锁,也就是把 t2 的 X 锁拿出来检测,看跟现有锁有哪些依赖。

t2 的 X 锁在等待 t1 的 S 锁,lock_trx 等于 start,成环死锁产生。

f2505f899667dc624e8d5efe2554f389.png

也就是:t1 的 insert 插入加了 S 锁,t2 的 X 锁虽然没加成功,但是真实存在,标记为等待状态。t1 再想获取 X 锁,发现与 t2 等待状态的 X 锁冲突。再次检测,发现 t2 等待状态的 X 锁与 t1 的 S 锁冲突,死锁产生。

我画了一个图方便你理解:

4d23095d2af3d1d1458f0b83078f64c1.png

后记

死锁分析是比较复杂的,调试源码可以比较清晰的理清思路,上面是我调试源码的一些结论,如果有理解有误的地方,记得及时帮我指出。

看完三件事❤️

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。

关注公众号 『 java烂猪皮』,不定期分享原创知识。

同时可以期待后续文章ing🚀

b24f963485b94b1c19cfb5e98aaed7ef.png

作者:挖坑的张师傅

出处:https://club.perfma.com/article/1982680

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值