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

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.lryc.cn/news/612406.html

相关文章:

  • Java 里的Tree初认识
  • 【概念学习】什么是深度学习
  • 12、Docker Compose 安装 Redis
  • Effective C++ 条款25:考虑写出一个不抛异常的swap函数
  • 【前端开发】五. ES5和ES6对比
  • imx6ull-驱动开发篇11——gpio子系统
  • 常用排序方法
  • 排序算法归并排序
  • 单变量单步时序预测:CNN-BiGRU卷积神经网络结合双向门控循环单元
  • 202506 电子学会青少年等级考试机器人六级实际操作真题
  • 基于RPR模型的机械臂手写器simulink建模与仿真
  • Qt Frameless Widget跨平台无边框窗口
  • 机器学习-KNN​​
  • 云平台托管集群:EKS、GKE、AKS 深度解析与选型指南-第四章
  • iT 运维: WindoWs CMD 命令表
  • Flutter开发 Image组件使用示例
  • <form> + <iframe> 方式下载大文件的机制
  • 基于Github Pages搭建个人博客站点:hexo环境搭建、本地预览与发布
  • 当前就业形势下,软件测试工程师职业发展与自我提升的必要性
  • AI 软件工程开发 AI 算法 架构与业务
  • [FBCTF2019]RCEService
  • Kafka-exporter采集参数调整方案
  • android NDK 报错日志解读和还原报错方法名
  • 数语科技登陆华为云商店,助力企业释放数据潜能
  • FPGA学习笔记——VGA彩条显示
  • 【软考系统架构设计师备考笔记4】 - 英语语法一篇通
  • 机器人定位装配的精度革命:迁移科技如何重塑工业生产价值
  • Spring Boot 参数校验全指南
  • 应急响应linux
  • vue3+element-plus,el-popover实现筛选弹窗的方法