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


2.从死锁日志的 22、24 行结合建表索引得知
22行:UPDATE test_table SET money = money + 4 WHERE user_id = 424行:Record lock, heap no 3 PHYSICAL RECORD: n_fields 2 compact format info bits 0事务2的 UPDATE test_table SET money = money + 4 WHERE user_id = 4 语句在持有锁:它通过普通索引 idx_user_id 更新 , 先获取了 user_id=4 的 X 锁 , 接着去申请对应行的主键(Record Lock)的行锁成功了 , 并不包括间隙锁(not gap) 。 具体是成功的哪个主键我们并不清楚 。
3.从死锁日志的 22、34 行结合建表索引得知
22行:UPDATE test_table SET money = money + 4 WHERE user_id = 434行: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的 UPDATE test_table SET money = money + 4 WHERE user_id = 4 语句在等待锁:它通过普通索引 idx_user_id 更新 , 先获取了 user_id=4 的 X 锁 , 接着去申请对应行的主键(Record Lock)的行锁但是被阻塞(waiting) , 并不包括间隙锁(not gap) 。 具体是哪个主键我们并不清楚 。
模糊结论肯定是有问题的 , 最大的问题在于导致的 SQL 语句不正确 , 即:死锁的原因是真实的 , 但是具体是因为哪些 SQL 导致的死锁是不清楚的 。 接着我们整理得到了以下可能有问题的表格:
mysql|线上频出MySQL死锁问题!分享一下教科书般的排查和分析过程
本文插图

可以得知 , 其实单从死锁日志分析是比较片面的 , 因为 user_id 为 4、5 这两个 update 操作是不会有互相阻塞的问题 , 肯定是有别的 SQL 因此 , 我们需要额外从业务日志分析才能还原完整的现场 。
4. 从业务日志分析
从死锁日志是不能完全知道导致的关键 SQL 和故障现场的整体流程 , 因此我们要借助业务日志来完成最后对故障现场的分析:通过前面对业务日志的分析 , 我们知道最关键的调用方法是 BizManagerImpl.transactionMoney , 查看对应源码:
@Override@Transactionalpublic boolean transactionMoney(List throws Exception { for (TransactionReqVO transactionReqVO : transactionReqVOList) { // 模拟业务操作 Thread.sleep(1000) int updateCount = testTableService.update(transactionReqVO.getUserId(), transactionReqVO.getMoney()) if (updateCount == 0) { log.error("转账异常:" + transactionReqVO) } } return true}
transactionReqVOList)
可以知道 , 应该是 for 循环事务的问题 , 但是具体是哪些 user_id 是不清楚的 , 接着我们查看业务日志的上下文 , 通过全链路 traceId(模拟) 做搜索 , 得到以下的日志:
[ConsumerThread2] org.example.controller.TestController : 全局链路跟踪id:2的日志:[TransactionReqVO(userId=4, money=4), TransactionReqVO(userId=2, money=2), TransactionReqVO(userId=5, money=5)][ConsumerThread1] org.example.controller.TestController : 全局链路跟踪id:1的日志:[TransactionReqVO(userId=5, money=5), TransactionReqVO(userId=1, money=1), TransactionReqVO(userId=4, money=4)]
分析到这一步 , 我们已经可以还原死锁场景了 , 事务流程图如下:
mysql|线上频出MySQL死锁问题!分享一下教科书般的排查和分析过程
本文插图

5. 业务日志、死锁日志结合分析
将死锁日志分析得出的不正确表格加上业务日志分析得出正确表格 , 我们得出最终带有理解的最终正确的事务表格:
mysql|线上频出MySQL死锁问题!分享一下教科书般的排查和分析过程


推荐阅读