生产上数据库死锁,是该程序员祭天了
Hello,大家好,我是 Skow
阅读这篇文章之前,大家可以问问自己
何为死锁?
Mysql 具有哪些锁?
Mysql 的锁模式兼容矩阵你是否清楚?
如何排查死锁问题?
如果你可以闭着眼睛回答出来这些问题的,那么就默默点赞离开👍🏻
如果你对上面的知识点,还有点含糊不清,那么这篇文章将会带你从一个真实业务场景入手,分析死锁问题,希望本文对你有所帮助,Let's go 🤨
业务背景
目前我司有两个系统 A 系统、B 系统
A 系统存放着公司所有人员的信息
B 系统需要日终定时从 A 系统同步数据
人员已在 B 系统中存在,则更新,不存在则插入
因人员信息过多,所以采取多线程方式同步人员数据
在验证代码的时候,😡测试人员怒气冲冲的反应, sync_user 也就是我们的同步人员的那张数据表打不开了
遇事莫慌,先甩锅运维,“小姐姐,莫急莫慌,肯定是数据库系统出问题了"
经过运维和 DBA 的排查,其实罪魁祸首是开发
我们的代码导致了这张表出现了死锁,从而导致表打不开了
那,到底是为何发生了死锁?接下来我们还原一下案发现场
案发还原
看一下原始的建表语句(当然不会给你看真实的表)
现在系统中有张三、李四两个用户
表已经准备就绪了,接下来看下我们的数据隔离级别、并且把我们的自动提交关闭
正戏开始!!! ✍️
按照下图模拟下我们并发同步数据的情况
开启 事务 1,执行更新语句 >UPDATE sync_user SET user_name = "张三 2" where login_account = "zhangsan";
开启 事务 2,执行更新语句 > UPDATE sync_user SET user_name = "李四 2" where login_account = "lisi"; 更新成功
回到事务 1,执行插入语句 > INSERT INTO sync_user (user_id, user_name, login_account) VALUES ('3', '王五', 'wangwu');-- 此条语句阻塞中
回到事务 2,执行插入语句 > INSERT INTO sync_user (user_id, user_name, login_account) VALUES ('4', '杨六', 'yangliu'); -- 出现死锁,并且事务 1 的插入语句执行成功
以上就是我们模拟的并发情况,课代表总结图如下 👇
死锁分析
通过事务 2 提示的 Deadlock found when trying to get lock; try restarting transaction
我们可以很明白的得到,这就是发生了死锁情况
那么,什么是死锁呢?
大学老师都是这样告诉我们的:死锁是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进造成死锁的四个必要条件
互斥条件:一个资源每次只能被一个进程使用;
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;
ok,什么是死锁和造成死锁的必要条件我们已经知道了,要产生死锁,必先有锁,那么 Mysql 有哪些锁呢?
Mysql 按照锁模式区分有:记录锁、gap 锁、next-key 锁、插入意向锁
具体的锁作用篇幅限制,就不展开说明
锁的兼容矩阵为:横行为当前已经持有的锁,纵向为正在请求的锁
接下来,正式分析一下事务 1、事务 2 各自拿到了什么锁
事务 1 在更新 zhangsan 张三的时候
间隙锁: UPDATE 语句会在非唯一索引的 login_account 加上间隙锁,即获得 (lisi,zhangsan)、(zhangsan,+∞)
记录锁:因为 login_account 为索引,会在 zhangsan 这一行加锁
Next-Key 锁:Next-Key 锁 = 记录锁 + 间隙锁,所以该 UPDATE 语句就有了 (lisi,zhangsan] 的 Next-Key 锁
综上所述:更新张三的语句获得了
Next-Key 锁
-> (lisi,zhangsan],Gap锁
-> (zhangsan,+∞)事务 1 在插入 wangwu 王五的时候
间隙锁: 因为 wangwu(在 lisi 和 zhangsan 之间),所以需要请求加 (lisi,zhangsan) 的间隙锁
插入意向锁(Insert Intention):插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即事务 A 需要插入意向锁 (lisi,zhangsan)
因此,事务 1 的 UPDATE 语句和 INSERT 语句执行完,它是持有了 (lisi,zhangsan] 的 Next-Key 锁,(zhangsan,+∞) 的 Gap 锁,想拿到 (lisi,zhangsan) 的插入意向排它锁
事务 2 的分析也如上举例,我们直接给出答案
事务 2 的 UPDATE 语句和 INSERT 语句执行完,它是持有了 (-∞,lisi] 的 Next-Key 锁,(lisi,zhangsan) 的 Gap 锁,想拿到 (lisi,zhangsan) 的插入意向排它锁
锁已经分析完毕了,接下来,我们需要去查看一下事务的日志结果
真相即将浮出水面
事务 1 期望拿到 (lisi,zhangsan) 的插入意向锁,但是这个范围当前被事务 2 的 (lisi,zhangsan] 的 gap 锁占有了,这两把锁又是冲突的
事务 2 期望拿到 (lisi,zhangsan) 的插入意向锁,但是这个范围被事务 1 的 (lisi,zhangsan] 的 Next-Key 锁占有了,这两把锁又是冲突的
所以死锁发生。因为 Innodb 的底层机制,它会让其中一个事务让出资源,另外的事务执行成功,这就是为什么你最后看到事务 1 插入成功了,但是事务 2 的插入显示了 Deadlock found
总结
死锁原因已经分析出来了,那我们以后面对死锁,整体解决思路是什么呢?
甩锅运维模拟死锁场景
show engine innodb status;查看死锁日志
找出死锁 SQL
SQL 加锁分析,这个可以去官网看哈
分析死锁日志(持有什么锁,等待什么锁)
熟悉锁模式兼容矩阵,InnoDB 存储引擎中锁的兼容性矩阵。
参考文章:
丁奇 《MySql 实战 45 讲》
捡田螺的小男孩 《手把手教你分析 Mysql 死锁问题》
文章结束 🤣
如果本文对你有所帮助的话,那就点个赞吧
更多分享尽在微信公众号【codeLiveHouse】
公众号回复 “资料” 可以获取大厂面试题/技术文档/电子书等等
版权声明: 本文为 InfoQ 作者【skow】的原创文章。
原文链接:【http://xie.infoq.cn/article/a8d9cb66db79ad2d933fc6989】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论