当前位置: 首页 > backend >正文

mysql死锁的常用解决办法

十分想念顺店杂可。。。

MySQL 死锁是并发场景中常见的问题,本质是两个或多个事务相互持有对方需要的锁,且都在等待对方释放锁,形成循环等待。解决死锁需从预防检测处理三个层面入手,以下是具体方案:

一、死锁的检测方法

首先需要确认死锁发生的具体场景,常用工具和命令:

  1. 查看 InnoDB 死锁日志
    执行 SHOW ENGINE INNODB STATUS;,在输出结果的LATEST DETECTED DEADLOCK部分可查看最近一次死锁的详细信息,包括:

    • 参与死锁的事务 ID、SQL 语句
    • 事务持有和等待的锁类型(行锁、表锁等)
    • 锁定的资源(行记录、索引等)
  2. 开启死锁日志记录
    my.cnf中配置,持久化记录死锁日志:

    innodb_print_all_deadlocks = 1  # 记录所有死锁(默认只记录最近一次)
    log_error = /var/log/mysql/error.log  # 日志存储路径
    

二、死锁产生的常见原因

  1. 事务操作顺序不一致
    多个事务操作相同表 / 行时,操作顺序不同会导致循环等待。
    例:事务 A 先更新表 1 再更新表 2,事务 B 先更新表 2 再更新表 1,可能形成死锁。

  2. 锁粒度不合理

    • 未使用索引导致行锁升级为表锁(InnoDB 行锁基于索引,无索引时会锁定全表)。
    • 范围查询(如WHERE id > 100)产生间隙锁(Gap Lock),扩大锁定范围。
  3. 事务持有锁时间过长
    事务中包含大量操作(如复杂计算、远程调用),导致锁长期不释放,增加冲突概率。

  4. 隔离级别不当
    较高的隔离级别(如REPEATABLE READ)会产生更多锁(如间隙锁),死锁概率更高。

三、解决死锁的核心方案

1. 统一事务操作顺序(最有效)

确保所有事务操作相同资源(表、行)时,严格遵循相同的顺序,避免循环等待。
例:所有事务必须先操作表 A,再操作表 B,最后操作表 C。

-- 错误示例(顺序相反导致死锁)
-- 事务A
UPDATE table1 SET col=1 WHERE id=1;  -- 持有table1的锁
UPDATE table2 SET col=1 WHERE id=1;  -- 等待table2的锁(被事务B持有)-- 事务B
UPDATE table2 SET col=1 WHERE id=1;  -- 持有table2的锁
UPDATE table1 SET col=1 WHERE id=1;  -- 等待table1的锁(被事务A持有)-- 正确示例(统一顺序)
-- 事务A和事务B都先操作table1,再操作table2
UPDATE table1 SET col=1 WHERE id=1;
UPDATE table2 SET col=1 WHERE id=1;
2. 减小事务范围,缩短锁持有时间
  • 事务只包含必要的 SQL 操作,避免无关逻辑(如计算、日志打印)。
  • 拆分大事务为多个小事务,减少单次锁定的资源量。
-- 优化前(大事务,锁持有久)
BEGIN;
UPDATE order SET status=1 WHERE id=100;  -- 锁定订单
SELECT * FROM user WHERE id=1;  -- 无关查询,延长锁持有时间
UPDATE log SET content='xxx' WHERE order_id=100;  -- 非核心操作
COMMIT;-- 优化后(小事务,仅保留核心操作)
BEGIN;
UPDATE order SET status=1 WHERE id=100;  -- 核心操作,快速提交
COMMIT;-- 非核心操作单独处理(无需锁定订单)
BEGIN;
UPDATE log SET content='xxx' WHERE order_id=100;
COMMIT;
3. 优化索引和查询,减少锁冲突
  • 确保查询使用索引:避免无索引导致的全表锁(InnoDB 行锁依赖索引)。
  • 避免锁定不必要的行:使用精确的WHERE条件,减少锁定范围;避免SELECT ... FOR UPDATE锁定过多行。
  • 慎用范围查询:范围查询(如id > 100)会产生间隙锁,可改用精确查询或降低隔离级别。

-- 错误示例(无索引导致表锁)
UPDATE user SET name='xxx' WHERE phone='13800138000';  -- phone无索引,锁定全表-- 正确示例(添加索引,仅锁单行)
ALTER TABLE user ADD INDEX idx_phone(phone);  -- 为phone添加索引
UPDATE user SET name='xxx' WHERE phone='13800138000';  -- 仅锁定符合条件的行
4. 调整事务隔离级别

InnoDB 默认隔离级别为REPEATABLE READ,该级别会产生间隙锁(防止幻读),死锁概率较高。若业务允许,可降低至READ COMMITTED

  • 减少间隙锁的使用(仅在外键约束和唯一性检查时保留)。
  • 释放锁更快(非匹配行的锁会提前释放)。
-- 临时修改当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;-- 永久修改(my.cnf)
transaction-isolation = READ-COMMITTED
5. 应用层重试机制

死锁发生后,MySQL 会自动回滚其中一个事务(“牺牲品”),应用程序可捕获1213错误码(死锁错误),重试事务:

// Java示例:捕获死锁错误并重试
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries) {try {// 执行事务逻辑executeTransaction();break;} catch (SQLException e) {// 1213是MySQL死锁错误码if (e.getErrorCode() == 1213) {retryCount++;// 短暂休眠后重试(避免立即重试再次冲突)Thread.sleep(100 * retryCount);} else {// 处理其他错误throw e;}}
}

6. 其他辅助手段
  • 使用表锁代替行锁:在高并发且表数据量小的场景,可主动使用LOCK TABLES强制表锁(需谨慎,会降低并发)。
  • 监控死锁趋势:通过performance_schema或第三方工具(如 Prometheus)监控死锁频率,及时发现异常。

总结

解决 MySQL 死锁的核心原则是:减少锁冲突概率 + 快速处理不可避免的冲突。通过统一操作顺序、优化事务和索引、调整隔离级别等预防措施,结合日志监控和重试机制,可有效降低死锁对业务的影响。

http://www.xdnf.cn/news/17115.html

相关文章:

  • 【面试场景题】电商秒杀系统的库存管理设计实战
  • 应急响应知识总结
  • centos KVM
  • git 清理submodule
  • Webpack核心技能:Webpack安装配置与模块化
  • 【YOLOv8改进 - C2f融合】C2f融合DBlock(Decoder Block):解码器块,去模糊和提升图像清晰度
  • C语言中的进程、线程与进程间通信详解
  • 前端UI组件库
  • XXL-JOB快速入门
  • 【数据分享】西藏土壤类型数据库
  • imx6ull-驱动开发篇11——gpio子系统
  • 大模型客户端工具如Cherry Studio,Cursor 配置mcp服务,容易踩的坑,总结
  • 力扣经典算法篇-44-组合总和(回溯问题)
  • 进程管理块(PCB):操作系统进程管理的核心数据结构
  • NineData 新增支持 AWS ElastiCache 复制链路
  • 开疆智能ModbusTCP转Profinet网关连接安川YRC1000机器人配置案例
  • Effective C++ 条款25:考虑写出一个不抛异常的swap函数
  • 每日任务day0806:小小勇者成长记之收获日
  • NAT转化
  • Knife4j:实时接口文档的利器
  • PyTorch生成式人工智能(26)——使用PyTorch构建GPT模型
  • 学习 Android (十六) 学习 OpenCV (一)
  • 基于PHP的论坛社交网站系统开发与设计
  • Spring Boot 参数校验全指南
  • [滑动窗口]904. 水果成篮
  • 基于PHP的快递管理系统的设计与实现
  • 【动态规划 | 01背包】动态规划经典:01背包问题详解
  • C++线程中 detach() 和 join() 的区别
  • FPGA学习笔记——VGA彩条显示
  • AVDTP Media Packet 传输全流程解析:从 SDP 到连接终止