终极指南:JGrowing实战案例解析MySQL死锁排查的完整过程记录
在日常开发中,MySQL死锁问题常常让开发者头疼不已。JGrowing(Java成长路线)项目中的实战案例为我们提供了一次深入理解死锁排查的绝佳机会。本文将通过一个真实案例,详细讲解如何从异常报警到最终解决MySQL死锁问题的完整过程,帮助开发者掌握实用的死锁排查技巧。
问题初现:阳光明媚下午的异常报警 ☀️
某天下午,系统突然抛出事务回滚异常,提示因死锁导致回滚。作为有一定MySQL锁知识基础的开发者,首先想到的是查看InnoDB状态获取死锁信息。通过执行以下命令:
SHOW ENGINE INNODB STATUS
得到的死锁日志显示,两个事务都在争夺同一行记录的X锁(行锁)。但与常见的循环等待型死锁不同,这里两个事务似乎在争夺同一个资源,这让排查工作陷入了困惑。
深入分析:从代码到表结构的全面排查 🔍
业务代码逻辑
查看相关业务代码发现,保存配置的逻辑存在潜在问题:
public int saveTenantConfig(PoiContext poiContext, TenantConfigDO tenantConfig) {
try {
return tenantConfigMapper.saveTenantConfig(poiContext.getTenantId(), poiContext.getPoiId(), tenantConfig);
} catch (DuplicateKeyException e) {
LOGGER.warn("[saveTenantConfig] 主键冲突,更新该记录。context:{}, config:{}", poiContext, tenantConfig);
return tenantConfigMapper.updateTenantConfig(poiContext.getTenantId(), tenantConfig);
}
}
这段代码的逻辑是:尝试插入记录,如果发生唯一索引冲突则进行更新。虽然可以用insert into ... on duplicate key update达到同样效果,但这并不能避免死锁问题。
表结构设计
问题表结构如下(简化处理):
CREATE TABLE `tenant_config` (
`id` bigint(21) NOT NULL AUTO_INCREMENT,
`tenant_id` int(11) NOT NULL,
`open_card_point` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_tenant` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT
其中tenant_id是唯一索引,插入和更新操作都基于该索引进行。
死锁产生的关键原因
通过分析业务日志发现,多个事务同时执行插入操作,导致唯一索引冲突后进入更新流程。在RR隔离级别下,插入操作需要检测唯一索引冲突,这会获取S锁(共享锁)。当多个事务同时持有S锁并尝试获取X锁(排他锁)时,就会产生死锁。
死锁发生过程演示 🎭
以下是死锁发生的简化时间线:
| 时间线 | 事务1 | 事务2 | 事务3 |
|---|---|---|---|
| 1 | insert into xx | insert into xx | insert into xx |
| 2 | 获取当前行X锁 | ||
| 3 | 需要检测唯一索引冲突获取S锁,阻塞 | 需要检测唯一索引冲突获取S锁,阻塞 | |
| 4 | commit; | 获取到S锁 | 获取到S锁 |
| 5 | 发现唯一索引冲突,执行Update语句(此时S锁未释放) | 发现唯一索引冲突,执行Update语句 | |
| 6 | 获取该行的X锁,被事务3的S锁阻塞 | 获取该行的X锁,被事务2的S锁阻塞 | |
| 7 | 发现死锁,回滚该事务 | update成功,commit; |
小提示:S锁是共享锁,X锁是互斥锁。X锁与S锁、X锁互斥,S锁与S锁不互斥。
解决方案:三种可行方案的对比与选择 ✅
方案一:降低隔离级别
将RR(可重复读)隔离级别降低为RC(读已提交)。RC隔离级别使用快照读,不会加S锁。但修改隔离级别可能影响其他业务,风险较高。
方案二:使用select for update加X锁
在插入前使用select * for update加X锁,避免获取S锁。这是比较理想的解决方案,不需要修改隔离级别,也不会引入分布式组件。
方案三:引入分布式锁
使用Redis或ZK等实现分布式锁,确保同一时间只有一个事务操作该记录。这种方法增加了系统复杂度,适用于更复杂的场景。
最终选择方案二,通过在插入前加X锁的方式避免了S锁竞争,从而解决死锁问题。
总结:死锁排查的黄金法则 📚
排查死锁问题时,不能仅依赖死锁日志,还需要结合业务代码、日志和表结构进行综合分析。掌握MySQL锁机制的基本知识至关重要,推荐阅读JGrowing项目中的为什么开发人员必须要了解数据库锁?一文深入学习。
通过本次实战案例,我们不仅解决了具体的死锁问题,更重要的是掌握了一套系统的死锁排查方法,为今后处理类似问题提供了宝贵经验。记住,在并发编程中,细节决定成败,深入理解底层原理才能写出更健壮的代码。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



