MySQL死锁问题分析与解决方案
AI 概述
MySQL死锁本质是事务循环等待对方释放锁,解决需从预防、检测、处理三方面入手。本文案例中,两不同更新操作因锁获取顺序不同致死锁。解决方案核心是统一锁获取顺序:创建统一更新DTO,用批量更新服务按固定顺序处理,改造现有业务代码。原理上,有序访问可破除循环等待,消除死锁。预防死锁应统一访问顺序、减少锁粒度等,数据库层面可优化索引等。统一锁获取顺序将循环等待转为线性等待,消除死锁且提升性能。
目录
文章目录隐藏

MySQL 死锁是并发场景中常见的问题,本质是两个或多个事务相互持有对方需要的锁,且都在等待对方释放锁,形成循环等待。正常解决死锁步骤需从预防、检测和处理三个层面入手。
问题现象
最近在高并发的业务系统中,我就遇到了一个典型的 MySQL 死锁问题。先看一下腾讯云数据库系统日志显示如下错误信息:
*** (1) TRANSACTION: TRANSACTION 17581947971, ACTIVE 32 sec fetching rows UPDATE business_table SET field1='value1', field2='value2', status=2, step_id=3 WHERE order_id ='ORDER_001'AND server_id =12345 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 728897 page no253458 n bits 448 index `idx_order_asset` waiting *** (2) TRANSACTION: TRANSACTION 17581942880, ACTIVE 64 sec starting index read UPDATE business_table SET ready_msg='操作失败:系统繁忙' WHERE order_id ='ORDER_002'AND asset_tag ='ASSET_ABC' *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 728897 page no253458 n bits 448 index `idx_order_asset` lock_mode X locks rec but not gap *** WE ROLL BACK TRANSACTION (1)
问题分析
死锁产生的根本原因
通过分析死锁日志,我们发现了问题的核心:

两个不同的更新操作:
- 操作类型 1:使用
order_id + server_id作为 WHERE 条件; - 操作类型 2:使用
order_id + asset_tag作为 WHERE 条件。
代码层面的问题
在业务代码中,我们发现了两个关键的 Mapper 方法:
/ 方法 1:更新业务状态
@Update("UPDATE business_table SET field1=#{field1}, status=#{status} " +
"WHERE order_id = #{orderId} AND server_id = #{serverId}")intupdateBusinessStatus(@Param("orderId") String orderId,
@Param("serverId") Integer serverId, ...);
// 方法 2:更新准备状态消息 @Update("UPDATE business_table SET ready_msg=#{readyMsg} " +
"WHERE order_id = #{orderId} AND asset_tag = #{assetTag}")intupdateReadyMessage(@Param("orderId") String orderId,
@Param("assetTag") String assetTag, ...);
死锁时序图

解决方案设计
核心思路:统一锁获取顺序
死锁的根本原因是不同事务以不同顺序获取锁资源。解决方案是强制所有事务按相同顺序获取锁。

实现方案
1. 创建统一的更新 DTO
@Data
@Builder
publicclassBatchUpdateDto {
private String orderId;
private String assetTag; // 统一使用 assetTag 作为定位字段
private Integer updateType; // 1-业务状态更新,2-消息更新
// 业务状态更新字段
private String field1;
private Integer status;
private String stepId;
// 消息更新字段
private String readyMsg;
}
2. 批量更新服务(使用 MyBatis BatchExecutor)
@Service
public class BatchUpdateService {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Transactional
public void updateSafely(List<BatchUpdateDto> updateList) {
if (CollectionUtils.isEmpty(updateList)) {
return;
}
// 关键:按 orderId + assetTag 排序,统一锁获取顺序
List<BatchUpdateDto> sortedList = updateList.stream()
.sorted(Comparator.comparing(BatchUpdateDto::getOrderId)
.thenComparing(BatchUpdateDto::getAssetTag))
.collect(Collectors.toList());
// 使用 BatchExecutor 进行高效批量更新
try (SqlSession batchSession= sqlSessionFactory.openSession(ExecutorType.BATCH)) {
BatchMapper batchMapper= batchSession.getMapper(BatchMapper.class);
int batchSize=50;
int count=0;
for (BatchUpdateDto updateDto : sortedList) {
batchMapper.updateSingle(updateDto);
count++;
// 定期提交,避免内存溢出
if (count % batchSize == 0) {
batchSession.flushStatements();
}
}
// 提交剩余记录
if (count % batchSize != 0) {
batchSession.flushStatements();
}
batchSession.commit();
}
}
}
3. 优化的 SQL 实现
<!-- 单条更新语句,配合 BatchExecutor 使用 -->
<update id="updateSingle">
UPDATE business_table
<set>
<if test="updateType == 1">
<!-- 业务状态更新 -->
<if test="field1 != null">field1=#{field1},</if>
<if test="status != null">status=#{status},</if>
<if test="stepId != null">step_id=#{stepId},</if>
</if>
<if test="updateType == 2">
<!-- 消息更新 -->
<if test="readyMsg != null">ready_msg=#{readyMsg},</if>
</if>
</set>
WHERE order_id = #{orderId} AND asset_tag = #{assetTag}
</update>
改造现有业务代码
改造前(会死锁)
@Service
public class BusinessService {
public void processOrders(List<Order> orders) {
// 危险:循环中的单独更新,锁顺序不可控
for (Order order : orders) {
if (needUpdateStatus(order)) {
mapper.updateBusinessStatus(order.getId(), order.getServerId(), ...);
}
if (needUpdateMessage(order)) {
mapper.updateReadyMessage(order.getId(), order.getAssetTag(), ...);
}
}
}
}
改造后(不会死锁)
@Service
publicclassBusinessService {
@Autowired
private BatchUpdateService batchUpdateService;
public void processOrders(List<Order> orders) {
// 收集所有更新操作
List<BatchUpdateDto> batchUpdates = newArrayList<>();
for (Order order : orders) {
if (needUpdateStatus(order)) {
BatchUpdateDtodto= BatchUpdateDto.builder()
.orderId(order.getId())
.assetTag(order.getAssetTag())
.updateType(1)
.status(order.getNewStatus())
.build();
batchUpdates.add(dto);
}
if (needUpdateMessage(order)) {
BatchUpdateDtodto= BatchUpdateDto.builder()
.orderId(order.getId())
.assetTag(order.getAssetTag())
.updateType(2)
.readyMsg(order.getMessage())
.build();
batchUpdates.add(dto);
}
}
// 批量安全更新,避免死锁
batchUpdateService.updateSafely(batchUpdates);
}
}
也可以事先对 orders 排序,然后保持原来的更新逻辑不变。
核心是统一访问顺序。
原理深度解析
死锁的四个必要条件
- 互斥条件:资源不能共享
- 无法破除:数据库锁的本质特性
- 持有并等待:持有资源的同时等待其他资源
- 无法破除:业务逻辑需要
- 不可剥夺:资源不能被强制释放
- 无法破除:事务 ACID 特性
- 循环等待:形成资源等待环路
- 可以破除:通过排序统一访问顺序
排序解决死锁的数学原理
无序访问的问题:
- N 个资源,可能的访问顺序:N! 种
- 任意两种不同顺序都可能产生死锁
- 死锁概率随并发度指数增长
有序访问的优势:
- N 个资源,访问顺序:1 种(固定排序)
- 不可能形成环路等待
- 死锁概率:0
3 个资源的访问:
- 无序访问:3!=6 种可能顺序
可能的死锁场景:
- T1:A→B→C
- T2:C→A→B
形成环路等待
- 有序访问:1 种固定顺序
不可能死锁:
- T1:A→B→C
- T2:A→B→C
线性等待
监控指标
- 死锁检测 SHOW ENGINE INNODB STATUS
- 事务等待时间 innodb_lock_wait_timeout
- 活跃事务数 INFORMATION_SCHEMA.INNODB_TRX
- 锁等待情况 performance_schema.data_locks
最佳实践总结
1. 预防死锁的设计原则
- 统一访问顺序:对所有资源按固定规则排序访问
- 减少锁粒度:使用行锁而非表锁
- 缩短事务时间:避免长事务
- 避免交叉访问:减少不同事务访问相同资源集合
2. 数据库层面优化
-- 优化索引,减少锁范围 ALTERTABLE business_table ADD INDEX idx_order_asset_optimized (order_id, asset_tag, status); -- 设置合理的锁等待超时 SET innodb_lock_wait_timeout =10; -- 开启死锁检测 SET innodb_deadlock_detect =ON;
结论
通过统一锁获取顺序这一简单而有效的方法,我们彻底解决了 MySQL 死锁问题。这个方案的核心思想是:
将潜在的循环等待转换为线性等待,从根本上消除死锁的可能性。
这种方法不仅解决了死锁问题,还带来了性能提升,是一个典型的”一石二鸟”的优化方案。在面对类似并发问题时,我们应该优先考虑从设计层面避免问题,其次一定要对数据库进行运行时的检测和恢复机制,将设计中的隐患暴露并解决。
以上关于MySQL死锁问题分析与解决方案的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。
声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » MySQL死锁问题分析与解决方案
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » MySQL死锁问题分析与解决方案

微信
支付宝