当前位置: 首页 > 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.dtcms.com/a/317490.html

相关文章:

  • 【Linux系统】进程间通信:命名管道
  • Java SPI 机制初探|得物技术
  • linux下的串口通信原理及编程实例
  • 二、Envoy静态配置
  • 时序预测(论文解读)-金融领域的滞后性
  • 客流特征识别准确率提升 29%:陌讯多模态融合算法在零售场景的实战解析
  • 【渲染流水线】[应用阶段]-[遮挡剔除]以UnityURP为例
  • NY112NY117美光固态闪存NY119NY123
  • 【Linux】重生之从零开始学习运维之主从MGR高可用
  • 在docker容器里面使用docker
  • 生成模型实战 | Transformer详解与实现
  • SQL的条件查询
  • 【Mysql】重生之从零开始学习运维之proxysql读写分离
  • docker相关操作记录
  • DSP2837X CLA开发实战教程
  • 解决Node.js v12在Apple Silicon(M1/M2)上的安装问题
  • 微软开发的Unix系统——Xenix测评
  • 运维新纪元:告别Excel手工规划,实现“零误差”决策
  • 无人机航拍数据集|第5期 无人机高压输电线铁塔鸟巢目标检测YOLO数据集601张yolov11/yolov8/yolov5可训练
  • Oracle开窗函数分类与统计应用
  • miniExcel一个对象加一个对象列表导出
  • 《Vue 3与Element Plus构建多语后台的深层架构》
  • 第一章-网络信息安全概述
  • 软考信息安全工程师11月备考
  • ZeroNews三步部署,安全远程访问教育内网
  • [激光原理与应用-165]:光机械件 - 影响系统性能指标的关键因素和敏感因素
  • 如何给小语种视频生成字幕?我的实测方法分享
  • VINS-Fusion+UWB辅助算法高精度实现
  • 【计算机网络 | 第3篇】物理媒介
  • Git 分支迁移完整指南(结合分支图分析)