事务原理与隔离级别 讲隔离级别时,反复提到 Next-Key Lock 防幻读、当前读加排他锁,但锁的细节全部推迟到了这里。MVCC 解决了读写不互相阻塞的问题,但当前读(SELECT ... FOR UPDATE、UPDATE、DELETE)和写操作仍然需要锁来保证正确性。
本文从两阶段锁(2PL)理论出发,拆解 InnoDB 的锁体系:表级锁(意向锁、MDL、AUTO_INC、全局锁)、行级锁(Record、Gap、Next-Key、Insert Intention Lock)、锁兼容性矩阵,以及 RR 下加锁规则的四个场景。然后是死锁的产生条件、Wait-For Graph 检测、死锁案例与排查 SOP,以及间隙锁误锁这个常见踩坑。MVCC 版本链与可见性判断见 MySQL MVCC 原理。
前置知识
了解 ACID 四大特性与四种隔离级别,参见 事务原理与隔离级别
了解 InnoDB 聚簇索引与二级索引的回表机制,参见 InnoDB 架构与实现
了解 MVCC 快照读与当前读的区别,参见 MySQL MVCC 原理
一、两阶段锁理论
1.1 2PL 原理
两阶段锁(Two-Phase Locking,2PL)是保证可串行化的经典并发控制协议。其规则是:每个事务的锁操作分为两个阶段,增长阶段只加锁不释放,收缩阶段只释放不新增。
2PL 为什么能保证可串行化?如果所有事务都遵守 2PL,事务的加锁顺序构成一个偏序关系,这个偏序关系就是可串行化顺序。形式化证明基于冲突可串行化理论,2PL 保证了冲突操作的串行顺序与加锁顺序一致。
1.2 严格两阶段锁(S2PL)
2PL 的问题是:收缩阶段释放锁后,其他事务可能读到该事务尚未提交的修改,这恰好是脏读。解决方案是严格两阶段锁(Strict 2PL,S2PL):所有锁在事务提交或回滚时才统一释放。S2PL 是大多数数据库 SERIALIZABLE 隔离级别的实现方式。
InnoDB 的 REPEATABLE READ 和 READ COMMITTED 在实际执行中采用了 S2PL 的变体:行级排他锁在事务提交时才释放,但共享锁的行为因隔离级别而异。RC 下共享锁在语句执行完就释放,RR 下共享锁持有到事务结束。
二、InnoDB 锁类型
2.1 锁体系总览
InnoDB 的锁体系从粒度到类型层层递进:
2.2 表级锁详解
InnoDB 的表级锁不止意向锁一类。总览图里出现的 MDL、AUTO_INC,以及更外层的全局锁,都属于表级或表级粒度以上的锁。它们触发机制各异,生产中踩坑的点也不同。这一节按”谁会触发、触发什么后果、生产怎么遇到”的顺序逐一展开。
2.2.1 元数据锁 MDL
元数据锁(Metadata Lock,MDL)是 Server 层加的锁,不是存储引擎层的锁,目的是防止 DDL 和 DML 并发时结构不一致。任何事务对表执行 SELECT、INSERT、UPDATE、DELETE 时,会自动在表上加 MDL 共享锁;执行 ALTER、DROP、CREATE 等 DDL 时,加 MDL 排他锁。
| 操作 | 加的 MDL | 持有时间 |
|---|---|---|
普通 SELECT | MDL 共享读锁(MDL_SHARED_READ) | 事务结束释放 |
INSERT / UPDATE / DELETE | MDL 共享写锁(MDL_SHARED_WRITE) | 事务结束释放 |
ALTER / DROP / TRUNCATE 等 DDL | MDL 排他写锁 | 拿到后执行,执行完释放 |
MDL 共享读锁与共享写锁都属于共享类,彼此兼容,所以多个事务可以同时读写。但 MDL 排他锁与所有 MDL 锁冲突,DDL 必须等表上所有事务的 MDL 释放后才能拿到。
生产中最经典的坑就在这里。一个长事务(比如一个跑了半小时的报表查询)持有表的 MDL 共享锁,此时 DBA 执行 ALTER TABLE 加字段。DDL 申请 MDL 排他锁,被长事务挡住,进入等待队列。问题在于 MDL 排他锁是排队的:排在 DDL 后面新来的 DML 也会被 DDL 的等待挡住,因为它们申请的共享锁与 DDL 要的排他锁冲突。于是长事务阻塞了 DDL,DDL 又反过来阻塞了所有后续查询,整张表瞬间不可读写,业务雪崩。
-- 长事务持有 MDL 读锁BEGIN;SELECT * FROM orders WHERE ...; -- 此事务未提交,持有 orders 的 MDL 共享锁
-- 另一个会话尝试 DDL,被阻塞ALTER TABLE orders ADD COLUMN remark VARCHAR(100);
-- 此时第三个会话的普通 SELECT 也会被阻塞SELECT * FROM orders LIMIT 1; -- 卡住,因为排在 DDL 后面排查 MDL 等待看 performance_schema.metadata_locks 和 sys.schema_table_lock_waits:
-- 查看谁持有、谁等待 MDLSELECT * FROM performance_schema.metadata_locksWHERE OBJECT_SCHEMA = 'testdb' AND OBJECT_NAME = 'orders';
-- sys 视图直接给出阻塞源SELECT * FROM sys.schema_table_lock_waits\G预防手段是 DDL 前先确认无长事务,或用 ALTER TABLE ... ALGORITHM=INSTANT(MySQL 8.0 部分场景支持秒级加列,不长时间持锁)。把 lock_wait_timeout 设小一点(默认 31536000 秒即一年,几乎等于不超时),可以让 DDL 等不到锁时快速失败而非无限期挂着。
2.2.2 AUTO_INC 锁
AUTO_INC 锁用于自增主键的分配。当 INSERT 语句需要给自增列分配新值时,InnoDB 要保证并发插入时自增值不重复、不跳号。根据 innodb_autoinc_lock_mode 的取值,有三种模式:
| 模式 | 取值 | 行为 | 适用 |
|---|---|---|---|
| 传统模式 | 0 | 全表级 AUTO_INC 锁,整条 INSERT 语句期间持有,串行 | 兼容旧版本,并发差 |
| 连续模式 | 1 | 批量插入用表级锁,单条插入用轻量锁(互斥量,拿完即释放) | 兼顾语义与并发,MySQL 8.0 前的默认值 |
| 交叉模式 | 2 | 全程用轻量锁,不锁表,多事务可并发分配 | 并发最高,MySQL 8.0 起的默认值 |
生产中最常见的纠结是模式 2 的跳号。模式 2 下批量 INSERT ... VALUES (...), (...), ... 时,InnoDB 一次性预分配一段自增值,若语句中途失败或回滚,预分配但未用的值就丢了,自增列出现空洞。对要求自增列严格连续的场景(比如生成对外展示的订单号),不能用模式 2,要用模式 1。对只把自增列当主键、不关心是否连续的场景,模式 2 并发最高,是 8.0 的默认值。
-- 查看当前模式SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';-- 8.0 默认为 2(交叉模式),5.7 默认为 1(连续模式)
-- 跳号场景:模式 2 下批量插入中途失败-- 假设 orders(id INT AUTO_INCREMENT PRIMARY KEY, amt INT)INSERT INTO orders(amt) VALUES (1),(2),(3),(4),(5); -- InnoDB 预分配 id=1..5-- 若语句失败,下次插入从 id=6 开始,预分配但未用的值丢弃,造成跳号2.2.3 全局锁
全局锁作用于整个数据库实例,效果是让整个库只能读不能写。MySQL 的全局锁是 FLUSH TABLES WITH READ LOCK(FTWRL):
FLUSH TABLES WITH READ LOCK;-- 此后全库所有表只能 SELECT,任何 INSERT/UPDATE/DELETE/DDL 都被阻塞-- 直到执行 UNLOCK TABLESFTWRL 的典型用途是全库逻辑备份。备份工具(如 mysqldump 的非事务一致性选项、或 Percona XtraBackup 的某些模式)在开始备份前执行 FTWRL,拿到一个全库一致的快照点,保证备份出来的多张表数据在同一个逻辑时刻一致。这是它唯一仍合理的生产用途。
但 FTWRL 代价极高。它阻塞所有写操作,业务停摆,且持有期间任何写请求堆积。更麻烦的是它可能和长事务互锁:如果某个长事务持有锁未释放,FTWRL 会等它,等的过程中全库写请求已经堆积。生产中能用一致性快照备份(mysqldump --single-transaction,依赖 MVCC 不加表锁)就别用 FTWRL,只在引擎不支持事务或需要跨引擎一致性时才用。
MySQL 8.0 后,需要”全库只读”语义时更推荐 SET GLOBAL read_only = ON 或 super_read_only = ON。它们让实例只读但不锁表,写请求被拒而非阻塞,且不会像 FTWRL 那样持有期间死等已有事务。两者的区别:read_only 允许有 SUPER 权限的用户写,super_read_only 连 SUPER 用户也不让写。
2.2.4 意向锁
意向锁是表级锁,但目的是为行级锁服务。如果事务要加表级锁,必须检查表中是否有行级锁。没有意向锁时需要逐行扫描,有了意向锁只需检查表级意向锁即可。意向锁是一个快速判断表内是否存在行级锁的标记。
| 锁类型 | 符号 | 兼容性 | 用途 |
|---|---|---|---|
| 共享锁(S Lock) | S | S-S 兼容,S-X 互斥 | 读操作(SELECT ... LOCK IN SHARE MODE) |
| 排他锁(X Lock) | X | 与所有锁互斥 | 写操作(INSERT / UPDATE / DELETE / FOR UPDATE) |
| 意向共享锁(IS) | IS | IS-IS / IS-IX / IS-S 兼容 | 表级锁,表示打算加行级 S 锁 |
| 意向排他锁(IX) | IX | IX-IX / IX-IS 兼容 | 表级锁,表示打算加行级 X 锁 |
-- 意向锁的工作方式-- 事务 T1:给 id=1 的行加 X 锁(自动加 IX 表锁)SELECT * FROM accounts WHERE id = 1 FOR UPDATE;-- InnoDB 自动在 accounts 表上加 IX 锁
-- 事务 T2:尝试给整个表加 S 锁LOCK TABLES accounts READ;-- 检查:表上有 IX 锁,与 S 锁冲突 → 阻塞等待2.3 锁兼容性矩阵
| IS | IX | S | X | |
|---|---|---|---|---|
| IS | 兼容 | 兼容 | 兼容 | 互斥 |
| IX | 兼容 | 兼容 | 互斥 | 互斥 |
| S | 兼容 | 互斥 | 兼容 | 互斥 |
| X | 互斥 | 互斥 | 互斥 | 互斥 |
意向锁之间互相兼容(IS-IS、IS-IX、IX-IX),因为它们只是标记”打算加行锁”,不真正锁住行。只有表级 S/X 锁与意向锁冲突时才阻塞。
2.4 行级锁:Record Lock
Record Lock 锁定索引记录本身,而非数据行。如果表没有索引,InnoDB 会使用隐藏的聚簇索引进行锁定。
-- Record Lock 示例-- 表:users(id INT PRIMARY KEY, name VARCHAR(50), age INT, INDEX idx_age(age))BEGIN;SELECT * FROM users WHERE id = 5 FOR UPDATE;-- 锁定:id=5 的 Record Lock(在聚簇索引上)
SELECT * FROM users WHERE age = 25 FOR UPDATE;-- 锁定:age=25 的 Record Lock(在 idx_age 索引上)-- + 对应主键的 Record Lock(回表后在聚簇索引上)2.5 行级锁:Gap Lock
Gap Lock 锁定索引记录之间的间隙,防止其他事务在间隙中插入新记录。Gap Lock 是 InnoDB 在 REPEATABLE READ 下防止幻读的关键机制。
-- Gap Lock 示例-- 假设 users 表中 age 列有值:10, 20, 30, 40BEGIN;SELECT * FROM users WHERE age = 20 FOR UPDATE;-- 在 RR 隔离级别下,对 age=20 命中的记录加 Next-Key Lock (10, 20]-- 并向右遍历到第一条不满足条件的 age=30,退化为纯 Gap Lock (20, 30)-- 即 idx_age 上:(10, 20] 的 Next-Key Lock + (20, 30) 的 Gap Lock(不含 age=30 本身)
-- 另一个事务尝试插入INSERT INTO users (name, age) VALUES ('New', 25);-- 阻塞!因为 25 落在 (20, 30) 间隙内Gap Lock 在 READ COMMITTED 下不会启用。如果应用从 RR 切换到 RC,间隙锁消失,幻读防护也会消失。有些团队为了减少锁冲突把隔离级别改成 RC,需要同步评估幻读风险。
2.6 行级锁:Next-Key Lock
Next-Key Lock = Record Lock + Gap Lock,锁定一条记录及其前面的间隙。这是 InnoDB 在 RR 隔离级别下的默认加锁方式。
-- Next-Key Lock 的范围表示-- 假设索引值为:5, 10, 15, 20-- Next-Key Lock 的可能范围:-- (-∞, 5] → 间隙(-∞,5) + 记录5-- (5, 10] → 间隙(5,10) + 记录10-- (10, 15] → 间隙(10,15) + 记录15-- (15, 20] → 间隙(15,20) + 记录20-- (20, +∞) → 间隙(20,+∞)2.7 行级锁:Insert Intention Lock
Insert Intention Lock 是 Gap Lock 的特殊形式,表示意图在间隙中插入。多个事务在同一间隙中插入不同位置时不会互相阻塞。事务 A 持有 (10, 20) 的 Gap Lock 时,事务 B(插入 age=15)和事务 C(插入 age=12)都阻塞;A 释放后,B 和 C 并行插入,因为它们插入的位置不同,互不冲突。
三、加锁规则
3.1 加锁规则
InnoDB 的加锁遵循以下规则(适用于 RR 隔离级别):
- 加锁的基本单位是 Next-Key Lock
- 查找过程中访问到的对象才会加锁
- 等值查询:唯一索引命中记录,退化为 Record Lock;未命中,退化为 Gap Lock
- 等值查询:普通索引命中记录,向右遍历到不满足条件的第一条记录,该记录的 Gap Lock 不包含
- 范围查询:对扫描到的每个索引记录加 Next-Key Lock
3.2 加锁规则实战分析
-- 加锁规则实战分析-- 表:t(id INT PRIMARY KEY, c INT, INDEX idx_c(c))-- 数据:id=5,c=5 | id=10,c=10 | id=15,c=15 | id=20,c=20
-- 场景1:等值查询,唯一索引命中SELECT * FROM t WHERE id = 10 FOR UPDATE;-- 加锁:id=10 的 Record Lock(退化)
-- 场景2:等值查询,唯一索引未命中SELECT * FROM t WHERE id = 12 FOR UPDATE;-- 加锁:(10, 15) 的 Gap Lock(退化)
-- 场景3:等值查询,普通索引命中SELECT * FROM t WHERE c = 10 FOR UPDATE;-- 加锁:idx_c 上 (5, 10] 的 Next-Key Lock-- idx_c 上 (10, 15) 的 Gap Lock(向右到不满足条件的第一条)-- 聚簇索引上 id=10 的 Record Lock(回表)
-- 场景4:范围查询SELECT * FROM t WHERE c >= 10 AND c < 15 FOR UPDATE;-- 加锁:idx_c 上 (5, 10] 的 Next-Key Lock-- idx_c 上 (10, 15] 的 Next-Key Lock-- 聚簇索引上 id=10, id=15 的 Record Lock场景 3 最容易出错。等值查询命中普通索引时,InnoDB 会向右遍历直到遇到不满足条件的第一条记录(c=15),但这条记录的 Gap Lock 不包含,即 (10, 15) 是 Gap Lock 而非 Next-Key Lock,不锁 c=15 这条记录本身。这个设计是为了减少锁范围,避免不必要的阻塞。
四、间隙锁误锁踩坑
间隙锁的目的是防止幻读,但在实际开发中经常导致意外的锁等待。典型场景是范围查询锁住了不存在的行,导致后续插入阻塞。
-- 间隙锁误锁场景-- 表:orders(id INT PRIMARY KEY, status VARCHAR(20), INDEX idx_status(status))-- 数据:status 列有 'paid', 'shipped',没有 'pending'(字母序 paid < pending < shipped)
-- 事务 A:查询某个不存在的状态BEGIN;SELECT * FROM orders WHERE status = 'pending' FOR UPDATE;-- 没有命中记录,但加了 Gap Lock-- 锁定了 ('paid', 'shipped') 之间的间隙(按字母序)
-- 事务 B:尝试插入INSERT INTO orders (id, status) VALUES (100, 'processing');-- 阻塞!'processing' 字母序也落在 ('paid', 'shipped') 间隙内-- 虽然事务 A 查的是 'pending',但 Gap Lock 覆盖整个间隙,无关的插入也被挡住事务 A 查询的 status = 'pending' 没有命中记录,InnoDB 退化为 Gap Lock,锁住的是索引中 'pending' 应该所在位置的前后间隙。'pending' 的字母序落在 'paid' 和 'shipped' 之间,所以锁住的是 ('paid', 'shipped') 这段间隙,任何插入到这个间隙的记录都会被阻塞,包括与 'pending' 毫无关系的 'processing'(它同样满足 paid < processing < shipped)。这里的关键是:Gap Lock 锁的是一段区间,不是某个具体值,只要插入值落进区间就阻塞,和事务 A 查的那个值是否相同无关。
-- 排查方法:查看当前锁等待(MySQL 8.0 用 performance_schema)SELECT * FROM performance_schema.data_lock_waits;SELECT * FROM performance_schema.data_locks;SELECT * FROM information_schema.INNODB_TRX;
-- 解决方法:-- 1. 缩小查询范围,加主键条件:WHERE id = ? AND status = 'pending'-- 2. 降低隔离级别到 READ COMMITTED(Gap Lock 不启用)-- 3. 如果业务允许,用普通 SELECT 代替 SELECT FOR UPDATE间隙锁误锁是线上锁等待的常见原因。排查时如果发现 data_locks 中有 lock_mode 为 GAP 的记录,且等待的事务是 INSERT,大概率是间隙锁误锁。解决方案优先考虑缩小查询范围或用普通 SELECT,轻易不要降隔离级别,那会引入幻读风险。
五、死锁
5.1 死锁产生条件
死锁需要同时满足四个条件(Coffman 条件):
- 互斥:资源同一时刻只能被一个事务持有
- 持有并等待:事务持有至少一个资源,同时等待其他资源
- 不可抢占:已持有的资源不能被强制剥夺
- 循环等待:事务之间形成环形等待链
5.2 死锁检测:Wait-For Graph
数据库通过等待图(Wait-For Graph)检测死锁:如果等待图中存在环,则存在死锁。
检测到环 T1→T2→T3→T1 后,数据库选择一个牺牲者(通常是修改数据量最少的事务)回滚,打破循环。InnoDB 默认开启死锁检测(innodb_deadlock_detect=ON),在持有锁和等待锁的事务之间构建 Wait-For Graph,发现环就回滚代价较小的一方。
5.3 死锁案例
-- 死锁案例:交叉更新-- 事务 A -- 事务 BBEGIN; BEGIN;UPDATE t SET c=100WHERE id=5;-- 持有:id=5 的 Record Lock
UPDATE t SET c=200 WHERE id=10; -- 持有:id=10 的 Record Lock
UPDATE t SET c=100WHERE id=10;-- 等待:id=10 的 Record Lock(被 B 持有)
UPDATE t SET c=200 WHERE id=5; -- 等待:id=5 的 Record Lock(被 A 持有) -- 死锁!InnoDB 检测到并回滚 BInnoDB 检测到死锁后,回滚其中一个事务并返回错误:
-- 被回滚的事务收到:-- ERROR 1213 (40001): Deadlock found when trying to get lock;-- try restarting transaction
-- 查看最近的死锁信息SHOW ENGINE INNODB STATUS\G-- 关注 "LATEST DETECTED DEADLOCK" 段,里面有死锁时刻两个事务的锁信息5.4 死锁预防策略
| 策略 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 超时机制 | 等待超过阈值则回滚 | 实现简单 | 阈值难设定,可能误杀长事务 |
| 按固定顺序访问 | 所有事务按相同顺序加锁 | 从根本上消除循环等待 | 需要业务层约束 |
| Wait-Die | 老事务等待新事务,新事务回滚 | 不会饿死老事务 | 新事务可能反复回滚 |
| Wound-Wait | 老事务抢占新事务的资源 | 老事务不会被阻塞 | 新事务可能反复被抢占 |
预防死锁最实用的方法是按固定顺序访问资源。如果 T1 和 T2 都按 id 升序加锁(先 id=5 再 id=10),就不会形成循环等待。这是最简单也最有效的死锁预防策略。
六、锁排查运维 SOP
6.1 锁等待参数
InnoDB 的锁等待超时由 innodb_lock_wait_timeout 控制:
50Optionalinteger
ONOptionalboolean
6.2 查锁 SOP
线上出现锁等待时,排查的标准流程如下:
-- 第一步:查看当前所有事务SELECT trx_id, trx_state, trx_started, trx_requested_lock_id, trx_wait_startedFROM information_schema.INNODB_TRX;
-- 第二步:查看当前锁信息(MySQL 8.0 用 performance_schema)SELECT * FROM performance_schema.data_locks;SELECT * FROM performance_schema.data_lock_waits;
-- MySQL 5.7 用 information_schema(INNODB_LOCKS 在 8.0 已删,5.7 才可用):-- SELECT * FROM information_schema.INNODB_LOCKS;-- SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- 第三步:找到阻塞源头-- trx_state = LOCK WAIT 的事务是被阻塞的-- MySQL 8.0 的锁信息在 performance_schema,与 INNODB_TRX 不在同一模型,-- 直接查 sys.innodb_lock_waits 视图(封装了 data_lock_waits + PROCESSLIST)更直观:SELECT * FROM sys.innodb_lock_waits\G
-- MySQL 5.7 的写法(INNODB_LOCK_WAITS 在 8.0 已删,5.7 才可用):-- SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, r.trx_query AS waiting_query,-- b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_query-- FROM information_schema.INNODB_LOCK_WAITS w-- JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id-- JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
-- 第四步:查看锁详情SHOW ENGINE INNODB STATUS\G-- 关注 "TRANSACTIONS" 段和 "LATEST DETECTED DEADLOCK" 段6.3 死锁日志解读
SHOW ENGINE INNODB STATUS 输出的 LATEST DETECTED DEADLOCK 段是死锁排查的关键。它记录了死锁发生时两个事务的状态:
*** (1) TRANSACTION:TRANSACTION 12345, ACTIVE 2 sec starting index readmysql tables in use 1, locked 1LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)MySQL thread id 10, OS thread handle 140123456789, query id 100 localhost root updatingUPDATE t SET c=100 WHERE id=10
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 50 page no 3 n bits 72 index PRIMARY of table `test`.`t`trx id 12345 lock_mode X locks rec but not gap waitingRecord lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; ...
*** (2) TRANSACTION:TRANSACTION 12346, ACTIVE 1 sec starting index readUPDATE t SET c=200 WHERE id=5
*** (2) HOLDS THE LOCK(S):RECORD LOCKS space id 50 page no 3 n bits 72 index PRIMARY of table `test`.`t`trx id 12346 lock_mode X locks rec but not gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:...Record lock, heap no 2 ...
*** WE ROLL BACK TRANSACTION (2)解读要点:
TRANSACTION 12345和TRANSACTION 12346是死锁的两个事务WAITING FOR THIS LOCK TO BE GRANTED显示事务正在等待什么锁HOLDS THE LOCK(S)显示事务当前持有什么锁lock_mode X locks rec but not gap是 Record Lock,lock_mode X是 Next-Key Lock- 最后一行
WE ROLL BACK TRANSACTION (2)说明 InnoDB 选择回滚事务 2(通常是因为它修改的数据量更少)
MySQL 8.0 将锁信息从 information_schema 迁移到了 performance_schema.data_locks 和 data_lock_waits(旧的 INNODB_LOCKS / INNODB_LOCK_WAITS 视图已删除,5.7 升级时需替换)。排查阻塞关系时,8.0 可直接用 sys.innodb_lock_waits 视图,省去手写 JOIN。如需更详细的锁状态,可用 SET GLOBAL innodb_status_output=ON 开启输出。
参考资料
- MySQL 8.0 Reference Manual: InnoDB Locking - InnoDB 锁类型官方文档,含 Record/Gap/Next-Key/Insert Intention Lock
- MySQL 8.0 Reference Manual: Deadlocks in InnoDB - 死锁检测、处理与排查
- Coffman et al., System Deadlocks (1971) - 死锁四个必要条件的经典论文
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






