写点什么

生产上数据库死锁,是该程序员祭天了

用户头像
skow
关注
发布于: 2021 年 07 月 26 日
生产上数据库死锁,是该程序员祭天了

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】


公众号回复 “资料” 可以获取大厂面试题/技术文档/电子书等等

发布于: 2021 年 07 月 26 日阅读数: 9
用户头像

skow

关注

分享,前方就是一片花海 2019.05.30 加入

微信公众号:codeLiveHouse

评论

发布
暂无评论
生产上数据库死锁,是该程序员祭天了