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】
公众号回复 “资料” 可以获取大厂面试题/技术文档/电子书等等
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。