记录一次mysql 死锁问题排查
一、死锁产生条件
1、hold and wait(占有并等待)
事务在持有至少一个资源的同时,又去请求其他事务占用的资源,并且在请求新资源时不释放已持有的资源。
例如 事务 T1 持有表 table1 中记录 R1 的锁,同时它又请求表 table2 中记录 R2 的锁;而事务 T2 持有记录 R2 的锁,同时请求记录 R1 的锁。在这个过程中,T1 和 T2 都不释放自己已经持有的锁,就满足了请求和保持条件。
2、循环等待条件
在发生死锁时,必然存在一个事务 - 资源的环形链,即事务集合 {T0,T1,T2,…,Tn} 中的 T0 正在等待一个 T1 占用的资源;T1 正在等待 T2 占用的资源,……,Tn 正在等待已被 T0 占用的资源。
例如事务 T1 持有资源 R1 并请求资源 R2,事务 T2 持有资源 R2 并请求资源 R3,事务 T3 持有资源 R3 并请求资源 R1,这样就形成了一个循环等待的关系,满足循环等待条件。
二、遇到的死锁情况和排查思路
研发找到DBA这里说他们产生了一次死锁,想让我们帮忙排查下,排查思路如下:第一反应看下死锁日志详细信息,我们是阿里云mysql,所以有页面上的死锁日志详细信息如下图,如果是自建的数据库,可以执行show engine innodb status 来查看最近一次的死锁信息,如果是想要打印每一次的死锁日志,需要开启死锁日志打印参数 innodb_print_all_deadlocks

如上图:
事务一 是持有了辅助索引的行锁,等待请求主键索引的行锁,where条件 batch_call_no 过滤后有几百行数据
事务二是持有了主键索引的行锁,等待请求辅助索引的行锁,主键id 过滤一条数据,这条数据在事务一的几百行数据中,所以引起了死锁
注意:这里如果对mysql锁机制不了解,建议先了解下mysql锁相关知识,下面介绍下这里的两个事务为什么会产生锁
事务一是根据辅助索引过滤,并且要回表,所以会产生辅助索引行锁和主键索引行锁(注意如果不需要回表是不会产生主键索引行锁的)
事务二 是根据主键id过滤,所以肯定会产生主键索引行锁,但是为什么会产生辅助索引行锁呢,这里就要说下根据主键索引过滤时产生辅助索引行锁的情况:
如果更新的字段在辅助索引里面,为了确保该条数据不被其他事务修改,也会对该条数据的辅助索引加锁的,结合本案例情况,我就想到是不是status字段和third_record_id 字段在辅助索引idx_call_no_status_result里,查了下该索引情况发现确实如此: KEY idx_call_no_status_result (batch_call_no,status,call_result,callcenter_detail_result) USING BTREE,
跟研发沟通后,了解到status 是个状态字段,只有1 2 3 4 5 五种状态,那其实这个字段在索引中没有任何用处的(如果不熟悉索引的建议先了解索引相关知识),跟研发沟通后决定去掉这个字段,观察几天后发现没再出现死锁。
三、避免死锁的常见方法
1、mysql 常见死锁场景
不同顺序访问表:事务1:先更新表A,再更新表B
事务2:先更新表B,再更新表A
相同记录的交叉更新:
事务1:更新记录X,然后更新记录Y
事务2:更新记录Y,然后更新记录X
间隙锁冲突:
在REPEATABLE READ隔离级别下,范围查询可能产生间隙锁
不同事务在不同范围内插入记录可能导致死锁

浙公网安备 33010602011771号