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

相关推荐
w_t_y_y17 小时前
Nginx Plus
运维·数据库·nginx
川贝枇杷膏cbppg18 小时前
dm_unknown_202512.log:达梦数据库 “未分类日志“
数据库·oracle
计算机毕设VX:Fegn089518 小时前
计算机毕业设计|基于springboot + vue图书商城系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
求学中--19 小时前
MySQL 数据库完整操作命令与使用指南
数据库·sql·mysql·oracle
DKunYu20 小时前
误删数据库表导致出现1146报错
数据库
惜分飞21 小时前
sql server 事务日志备份异常恢复案例---惜分飞
前端·数据库·php
sunddy_x21 小时前
MySQL入门
数据库·mysql
_Minato_21 小时前
数据库知识整理——数据库设计的步骤
数据库·经验分享·笔记·软考
hssfscv1 天前
Mysql学习笔记——事务
笔记·学习·mysql
坐吃山猪1 天前
BrowserUse14-源码-ScreenShot模块-整理
linux·数据库·python