问题背景
近期测试中,发现几年前开发的业务流程申请模块在频繁操作时会出现异常提示,导致审批流程失败。最初以为是代码逻辑不周或异常处理不足等常见错误,但通过日志排查后发现,问题源自数据库的死锁。以下是日志信息:
根据日志可以清晰的看到一个事务在获取锁时发生了死锁。
分析 MySQL 错误日志
进一步分析 MySQL 错误日志,发现以下死锁情况:
图片
事务一:
- 事务 ID: 6536211
- 状态: 活跃 10 秒,正在获取行锁
- 操作: 更新 record_process_audit_apply_main_data 表
- 等待的锁: 等待主键索引上的记录锁
- sql: UPDATE record_process_audit_apply_main_data SET update_account='dxwang', update_name='汪冬雪', update_time='2024-11-15 15:17:34.216', task_node='16' WHERE (task_id = '1857321847525548034')
事务二:
- 事务 ID: 6536206
- 状态: 活跃 5 秒,正在开始索引读取
- 持有的锁: 表 pdm.record_process_audit_apply_main_data 的主键索引上的记录锁。
- 等待的锁: 正在等待在表 pdm.record_process_audit_apply_main_data 的主键索引上的记录锁
- sql: UPDATE record_process_audit_apply_main_data SET update_account='dywang3', update_name='王冬艳', update_time='2024-11-15 15:17:38.009', task_node='15', apply_time='2024-11-15 15:17:33.085' WHERE (task_id = '1857322026072875010')
MySQL 最终决定回滚事务 2(事务 ID: 6536206)以解决死锁问题。
死锁原因
死锁发生的原因在于:
- 事务 1 和事务 2 都在尝试更新 record_process_audit_apply_main_data 表中的记录。
- 事务 1 正在等待事务 2 持有的锁,事务 2 也在等待事务 1 持有的锁,导致了死锁。
定位代码
通过进一步分析代码,发现以下关键方法:submitApplication。该方法及其调用的其他方法都在同一个事务中执行,可能导致事务时间过长,增加了锁竞争和死锁的风险。核心代码如下所示:
图片
图片
图片
图片
优化思路:
- 拆分事务:将大事务拆分成多个小事务,每个事务只处理一部分逻辑。确保每个事务尽可能短,减少锁持有时间。
- 异步处理:使用异步任务处理耗时操作,如调用第三方接口。使用消息队列将部分操作异步化。
- 优化数据库操作:确保所有涉及的表都有适当的索引,减少查询和更新的时间。使用批量操作,减少与数据库的交互次数。
- 事务隔离级别:根据业务需求选择合适的事务隔离级别,减少锁的竞争。
- 日志记录:记录事务的开始和结束时间,以及关键操作的执行情况,便于问题排查。
优化后的伪代码: