MySQL InnoDB 行锁与死锁排查实战演示

MySQL InnoDB 行锁与死锁排查实战演示

💡 导语

在高并发环境下,MySQL InnoDB 存储引擎的行锁与唯一索引机制经常成为性能瓶颈。

本文通过一个可复现的示例,系统演示如何分析 事务锁等待死锁问题 ,并利用 performance_schema.data_locks 精确定位锁冲突来源。

阅读本文,你将掌握:

  • InnoDB 行锁、间隙锁、插入意向锁的原理
  • 死锁形成过程及排查思路
  • performance_schema 的实战使用方法

目录


零:环境信息

mysql版本:8.0.32

innodb引擎

一、建表 SQL

sql 复制代码
CREATE DATABASE IF NOT EXISTS demo;
USE demo;
DROP TABLE IF EXISTS orders;

CREATE TABLE orders (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(20) NOT NULL UNIQUE,
    user_id BIGINT NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

说明:

  • order_no 为唯一索引字段,用于演示唯一约束下的锁行为
  • 使用 InnoDB 引擎
  • 默认事务隔离级别为 REPEATABLE READ

二、事务演示场景

我们模拟三个事务 A、B、C,均尝试插入同一个唯一索引值 'A10004'

事务 操作 状态
A 开启事务并插入 'A10004',不提交 持有锁
B 开启事务插入 'A10004' 阻塞等待 A
C 开启事务插入 'A10004' 阻塞等待 B
A 回滚事务 唤醒 B
B 插入成功后提交 C 被检测为死锁报错

三、执行流程与锁分析

1. 事务 A 插入

BEGIN;

INSERT INTO orders(order_no, user_id, amount) VALUES ('A10004', 1, 100.00);

锁行为:

  • 对唯一索引 'A10004'X(排他)锁
  • 对表加 IX(意向排他)锁
  • 状态:锁获取成功,但事务未提交

2. 事务 B 插入

BEGIN;

INSERT INTO orders(order_no, user_id, amount) VALUES ('A10004', 2, 100.00);

锁行为:

  • 尝试加 S(共享)锁检查 'A10004' 是否存在
  • 'A10004' 被事务 A 持有 X 锁,B 阻塞
  • 此时 B 尚未进入插入阶段(未申请 XINSERT_INTENTION 锁)

3. 事务 C 插入

BEGIN;

INSERT INTO orders(order_no, user_id, amount) VALUES ('A10004', 3, 100.00);

锁行为:

  • 同样尝试加 S 锁检查唯一性
  • 'A10004' 被 A 阻塞
  • 队列等待顺序:B → C

4. 事务 A 回滚

ROLLBACK;

行为:

  • 释放 'A10004' 上的 X
  • 唤醒等待队列
  • B 被唤醒执行,C 继续等待

5. 事务 B 执行插入

执行步骤:

  • 唤醒后加 S 锁检查唯一性
  • 发现 'A10004' 不存在
  • 申请 XINSERT_INTENTION
  • 执行插入并升级为 X
  • 插入成功,锁保持至提交或回滚

6. 事务 C 唤醒并触发死锁

分析过程:

  • 唤醒后尝试加 S 锁检查唯一性
  • 'A10004' 已被 B 插入并持有 XINSERT_INTENTION
  • C 需要等待 B 的锁,形成循环等待
  • InnoDB 死锁检测机制触发,C 被回滚
    报错信息:
    ERROR 1213 (Deadlock): Deadlock found when trying to get lock; try restarting transaction

四、锁类型解析

锁类型 含义 作用
X 排他锁 禁止其他事务对相同索引记录加 XS
S 共享锁 允许其他事务加 S 锁,但阻止 X
X, INSERT_INTENTION 插入意向锁 对间隙加锁,保证插入唯一值时的顺序性
S, GAP 共享间隙锁 锁定索引间隙,防止其他事务插入相同值,但允许读取

五、排查技巧

  1. 查看当前事务
    SELECT * FROM information_schema.innodb_trx;
  2. 查看锁信息
    SELECT * FROM performance_schema.data_locks\G;
    关键字段说明:
    | 字段 | 含义 |
    |---|---|
    | LOCK_MODE | 锁类型(X / S / GAP 等) |
    | LOCK_STATUS | 锁状态:GRANTED(已获锁)或 WAITING(等待中) |
    | OBJECT_INSTANCE_BEGIN | 锁对象地址,可用于定位索引记录 |
    | THREAD_ID | 对应会话线程 ID |
  3. 常见问题判断
    | 现象 | 排查思路 |
    |---|---|
    | 锁等待 | 查看 LOCK_STATUS=WAITING 的记录 |
    | 死锁 | 查看 SHOW ENGINE INNODB STATUS 或错误日志中的 ERROR 1213 |
    | 超时 | 检查 innodb_lock_wait_timeoutERROR 1205 日志 |

六、总结

  • InnoDB 唯一索引插入流程:

    • S 锁检查唯一性
    • 申请 XINSERT_INTENTION
    • 最终升级为 X
  • 锁等待顺序:

    • InnoDB 遵循 FIFO 队列
    • 前一个事务释放锁后,等待事务依次被唤醒
  • 死锁检测机制:

    • 当多个事务互相等待彼此持有的锁时,InnoDB 会主动检测并回滚其中一个事务以打破死锁
  • 实践建议:

    • 使用 performance_schema.data_locks 精确分析锁类型与等待对象
    • 避免长事务或高并发下重复插入相同唯一键
    • 通过减少唯一索引竞争、控制事务粒度来降低死锁风险

✍️ 结语:

死锁是并发系统中无法完全避免的现象,但通过合理的事务控制与锁分析手段,可以让我们快速定位并解决问题。 如果你在实际项目中遇到复杂锁等待场景,不妨打开 performance_schema.data_locks 查看锁信息,很多答案都在那儿。

相关推荐
拍客圈4 小时前
数据主站+副站做的设置
数据库
计算机学长felix5 小时前
基于SpringBoot的“面向校园的助力跑腿系统”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·后端
金仓拾光集6 小时前
__工艺数据管理的范式转变:金仓数据库替代MongoDB实操实践__
数据库·mongodb
xiaogg36786 小时前
redis-cluster集群配置部署
数据库·redis·缓存
运维小文6 小时前
MySQL高可用方案MIC&mysqlCluster+mysqlRouter
数据库·mysql·mic·mysql高可用·mysqlcluster·mysqlrouter
不剪发的Tony老师6 小时前
Redis Commander:一款基于Web、免费开源的Redis管理工具
数据库·redis
IT教程资源C6 小时前
(N_157)基于springboot,vue服装商城系统
mysql·vue3·前后端分离·springboot服装商城
金仓拾光集6 小时前
__金仓数据库替代MongoDB护航医疗隐私:医院患者随访记录安全存储实践__
数据库·安全·mongodb
Tiandaren7 小时前
自用提示词02 || Prompt Engineering || RAG数据切分 || 作用:通过LLM将文档切分成chunks
数据库·pytorch·深度学习·oracle·prompt·rag