Hello,大家好,我是 Skow
阅读这篇文章之前,大家可以问问自己
-
何为死锁? -
Mysql具有哪些锁? -
Mysql 的锁模式兼容矩阵你是否清楚? -
如何排查死锁问题?
如果你可以闭着眼睛回答出来这些问题的,那么就默默点赞离开👍🏻
如果你对上面的知识点,还有点含糊不清,那么这篇文章将会带你从一个真实业务场景入手,分析死锁问题,希望本文对你有所帮助,Let’s go 🤨
业务背景
目前我司有两个系统 A 系统、B系统
A 系统存放着公司所有人员的信息
B 系统需要日终定时从 A 系统同步数据
人员已在 B 系统中存在,则更新,不存在则插入
因人员信息过多,所以采取多线程方式同步人员数据
在验证代码的时候,😡测试人员怒气冲冲的反应, sync_user 也就是我们的同步人员的那张数据表打不开了
遇事莫慌,先甩锅运维,“小姐姐,莫急莫慌,肯定是数据库系统出问题了”
经过运维和DBA的排查,其实罪魁祸首是开发
我们的代码导致了这张表出现了死锁,从而导致表打不开了
那,到底是为何发生了死锁?接下来我们还原一下案发现场
案发还原
看一下原始的建表语句(当然不会给你看真实的表)
CREATE TABLE `sync_user` (
`user_id` VARCHAR ( 32 ) NOT NULL COMMENT '用户 ID',
`user_name` VARCHAR ( 32 ) DEFAULT NULL COMMENT '用户姓名',
`login_account` VARCHAR ( 50 ) DEFAULT NULL COMMENT '登陆账号',
PRIMARY KEY ( `user_id` ),
KEY `idx_login_account` ( `login_account` ) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = '用户信息表';
现在系统中有张三、李四两个用户

表已经准备就绪了,接下来看下我们的数据隔离级别、并且把我们的自动提交关闭


正戏开始!!!✍️
按照下图模拟下我们并发同步数据的情况
-
开启 事务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】
公众号回复 “资料” 可以获取大厂面试题/技术文档/电子书等等
往期推荐

策略+IOC 消灭ifelse,拿来吧你

面试官竟然和我死磕Maven

来自大厂的11条异常最佳实践

点个在看你最好看
原文始发于微信公众号(Issues):同步个数据,都能死锁?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/107384.html