如何破解 MySQL 死锁?核心原则与实操方法

技术群内再度热议:"监控系统告警频发------数据库出现严重死锁警告,订单业务几近停滞。"

新入职的同事小王看到群内消息,心中不禁一沉:"这不是我刚接手的项目吗?我只是增加了几个简单的更新操作啊......"小王仔细查看群中反馈的监控截图与运行日志,在困惑之余更添几分忐忑与焦急,连忙向资深同事请教。老同事耐心解释道:"你这是不小心踩中了 MySQL 的死锁陷阱......"

何谓死锁?简而言之,死锁犹如两人相遇于独木桥:

甲自东向西,乙自西向东

两人行至桥中,互不相让

最终双双无法通过

在 MySQL 中,当两个或更多事务相互等待对方释放持有的锁时,便会形成死锁。

小王所遇的死锁场景:

sql

CREATE TABLE orders (

id BIGINT PRIMARY KEY,

user_id BIGINT,

amount DECIMAL(10,2),

status VARCHAR(20),

version INT

);

CREATE TABLE account_balance (

user_id BIGINT PRIMARY KEY,

balance DECIMAL(10,2),

frozen_amount DECIMAL(10,2)

);

死锁代码示例

java

// 小王的业务逻辑------同时更新订单与账户

@Service

public class OrderService {

@Transactional

public void processOrder(Long orderId, Long userId) {

// 先更新订单状态

updateOrderStatus(orderId, "PROCESSING");

// 再冻结账户金额

freezeUserBalance(userId, 100.00);

}

@Transactional

public void cancelOrder(Long orderId, Long userId) {

// 先解冻账户金额

unfreezeUserBalance(userId, 100.00);

// 后更新订单状态

updateOrderStatus(orderId, "CANCELLED");

}

}

并发执行时的死锁场景:

时间线:

  1. 线程A:开始执行 `processOrder(1, 100)`

  2. 获取 `orders` 表中 `id=1` 的行锁

准备获取 `account_balance` 表中 `user_id=100` 的行锁

  1. 线程B:开始执行 `cancelOrder(2, 100)`

  2. 获取 `account_balance` 表中 `user_id=100` 的行锁

准备获取 `orders` 表中 `id=2` 的行锁

  1. 死锁发生!

  2. 线程A等待线程B释放 `account_balance` 的锁

线程B等待线程A释放 `orders` 的锁

相互等待,形成死锁

死锁带来的影响

  1. 业务停滞:相关订单无法继续处理

  2. 用户体验下降:用户面临操作超时

  3. 系统资源浪费:连接池占满,CPU 空转消耗

  4. 数据不一致风险:部分事务被迫回滚

如何有效避免死锁?

  1. 统一操作顺序

java

// 优化后:始终先操作订单表,再操作账户表

@Service

public class OrderService {

@Transactional

public void processOrder(Long orderId, Long userId) {

updateOrderStatus(orderId, "PROCESSING");

freezeUserBalance(userId, 100.00);

}

@Transactional

public void cancelOrder(Long orderId, Long userId) {

updateOrderStatus(orderId, "CANCELLED"); // 先操作订单表

unfreezeUserBalance(userId, 100.00); // 再操作账户表

}

}

  1. 设置事务超时

java

@Transactional(timeout = 3) // 设定事务超时时间

public void processOrderWithTimeout(Long orderId, Long userId) {

// 业务逻辑

}

  1. 降低事务粒度

java

// 将大事务拆分为小事务

public void processOrderInSmallTransactions(Long orderId, Long userId) {

updateOrderStatus(orderId, "PROCESSING");

// 账户操作置于独立事务中

TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);

transactionTemplate.execute(status > {

return freezeUserBalance(userId, 100.00);

});

}

  1. 采用乐观锁机制

sql

UPDATE account_balance

SET balance = balance 100, version = version + 1

WHERE user_id = 100 AND version = {oldVersion};

  1. 死锁检测与重试策略

java

@Slf4j

@Service

public class OrderService {

private static final int MAX_RETRY = 3;

public void processOrderWithRetry(Long orderId, Long userId) {

int retryCount = 0;

while (retryCount < MAX_RETRY) {

try {

processOrder(orderId, userId);

return; // 执行成功则退出

} catch (DeadlockLoserDataAccessException e) {

retryCount++;

log.warn("检测到死锁,正在进行第{}次重试", retryCount);

if (retryCount == MAX_RETRY) {

throw e;

}

// 采用指数退避策略

try {

Thread.sleep(100 * (long) Math.pow(2, retryCount));

} catch (InterruptedException ie) {

Thread.currentThread().interrupt();

throw e;

}

}

}

}

}

死锁排查工具

  1. 查看死锁日志

sql

查看近期死锁信息

SHOW ENGINE INNODB STATUS;

开启死锁日志记录

SET GLOBAL innodb_print_all_deadlocks = ON;

  1. 监控锁状态

sql

查看当前锁信息

SELECT * FROM information_schema.INNODB_LOCKS;

SELECT * FROM information_schema.INNODB_LOCK_WAITS;

避免死锁的核心原则

  1. 保持一致的访问顺序:对多个资源按固定顺序进行访问

  2. 控制事务粒度:尽量使事务轻量化,缩短锁持有时间

  3. 合理设计索引:确保查询利用索引,减少锁竞争范围

  4. 设置事务超时:为事务配置合理的超时时间

  5. 实现重试机制:对可能引发死锁的操作引入重试逻辑

总结:防范优于补救。死锁虽是并发系统中的常见问题,但通过良好的架构设计与编码实践,完全能够规避其发生。在系统设计与编码阶段即充分考虑并发安全,方能保障系统持续稳定运行。

来源:小程序app开发|ui设计|软件外包|IT技术服务公司-木风未来科技-成都木风未来科技有限公司

相关推荐
AFinalStone8 分钟前
Android 7系统无障碍服务(六)输入事件拦截与 TouchExplorer
android·无障碍服务
天空之城--25 分钟前
MT管理器Android逆向工程完全指南:从入门到实战
android
Anhty2 小时前
2026九月最新变声器测评:iOS安卓双端适配,低延迟运行更稳定
android·人工智能·功能测试·ios·智能手机
渡我白衣3 小时前
并查集:基础认识与模拟实现
android·java·javascript·数据结构·c++·算法·并查集
Android-Flutter5 小时前
flutter GetX 详解
android·flutter
MyFreeIT6 小时前
Android Manual
android
hai_android7 小时前
AIDL 实现 IPC 进程间通信
android·android studio
AFinalStone9 小时前
Android 7系统无障碍服务(五)AccessibilityNodeInfo 的构建与查询
android·无障碍服务
今天要早睡_9 小时前
C++ 核心语法速过:命名空间、引用、函数重载与 nullptr 深度解析
android·java·c++
啦啦啦~~~22210 小时前
PC端+安卓端阅读器推荐!开源本地小说阅读器软件,
android·论文阅读·windows·开源软件·福昕阅读器