mysql|线上频出MySQL死锁问题!分享一下教科书般的排查和分析过程( 三 )


关键点总结如下:
1.该库中最近一次死锁发生的时间是什么时候?
4行:2020-07-14 23:34:29 0x7f958f1d5700得知 , 最近一次死锁发生在 2020-07-14 23:34:292.该次死锁导致的两个事务的重要信息?
12行:RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146580 lock_mode X locks rec but not gap waiting得知 , 事务 1 等待的锁为:lock_mode X locks rec but not gap waiting24行:RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap得知 , 事务 2 持有的锁为:lock_mode X locks rec but not gap34行:RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap waiting得知 , 事务 2 等待的锁为:lock_mode X locks rec but not gap waiting39行:*** WE ROLL BACK TRANSACTION (2)得知 , 最后回滚的是事物 1从 12、24、34 行:index idx_user_id of table mall.test_table得知:导致该次死锁的索引为:idx_user_id3.能知道导致死锁的两个具体 SQL 吗?
不能 , 产生死锁的情况各式各样 , 事务中的 SQL 可能不止有两个 SQL , 单从死锁日志是没法知道具体原因的 , 必须要结合业务代码查看事务上下文查看2. 理论知识
排查过程中发现有个特点 , 影响的都是是线上的大用户 。 由于当时我很久没看死锁相关的理论知识 , 因此先去了解下相关死锁的基本知识 。
2.1 死锁的条件
1. 互斥条件:一个资源每次只能被一个进程使用 。
2. 占有且等待:一个进程因请求资源而阻塞时 , 对已获得的资源保持不放 。
3. 不可强行占有:进程已获得的资源 , 在未使用完之前 , 不能强行剥夺 。
4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系 。
破坏死锁也很简单 , 四个条件破一个即可 。 (本案例是破坏的 4)
2.2 数据库的锁类型
数据库的死锁比较复杂 , 主要是由 Insert、Update(其实在开发中 Delete 或 For Update 是不怎么不考虑的 , 因为在实际业务代码中我们一般不会有 Delete 或 For Update 的操作 , 删除都是物理删除 , for update 建议少用 , 除非你知道非用不可) 。 InnoDB 的锁:
1. 共享锁与独占锁(S、X)
2. 意向锁
3. 记录锁(Record Locks)
4. 间隙锁(Gap Locks)
5. Next-Key Locks
6. 插入意向锁
7. 自增锁
8. 空间索引断言锁
这里参考了官网的 Innodb 锁分类 , 从死锁日志的 lock_mode X locks rec but not gap, 大致能知道 , 这里可能涉及了 X 锁、记录锁、间隙锁(但是有个 not , 说明不涉及) 。
3. 从死锁日志分析
分析之前先得到该表的建表语句:show create table test_table:
CREATE TABLE `test_table` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `money` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
接着结合死锁日志、锁的种类、建表语句得出以下模糊的结论:
1.从死锁日志的 10、12 行结合建表索引得知
10行:UPDATE test_table SET money = money + 5 WHERE user_id = 512行:RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146580事务1的 UPDATE test_table SET money = money + 5 WHERE user_id = 5 语句在等待锁:它通过普通索引 idx_user_id 更新 , 先获取了 user_id=5 的 X 锁 , 接着去申请对应行的主键(Record Lock)的行锁但是被阻塞(waiting) , 并不包括间隙锁(not gap) 。 具体是哪个主键我们并不清楚 。


推荐阅读