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 查看锁信息,很多答案都在那儿。

相关推荐
TDengine (老段)9 分钟前
TDengine Python 连接器进阶指南
大数据·数据库·python·物联网·时序数据库·tdengine·涛思数据
赵渝强老师17 分钟前
【赵渝强老师】OceanBase的配置文件与配置项
数据库·oceanbase
玖日大大1 小时前
OceanBase SeekDB:AI 原生数据库的技术革命与实践指南
数据库·人工智能·oceanbase
高溪流2 小时前
3.数据库表的基本操作
数据库·mysql
alonewolf_992 小时前
深入剖析MySQL锁机制与MVCC原理:高并发场景下的数据库核心优化
数据库·mysql
一 乐3 小时前
绿色农产品销售|基于springboot + vue绿色农产品销售系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·宠物
黄宝康3 小时前
sqlyog密钥亲测有效
mysql
Codeking__3 小时前
Redis初识——什么是Redis
数据库·redis·mybatis
YIN_尹3 小时前
【MySQL】数据类型(上)
android·mysql·adb
k***1953 小时前
Spring 核心技术解析【纯干货版】- Ⅶ:Spring 切面编程模块 Spring-Instrument 模块精讲
前端·数据库·spring