代码改变世界

深入解析MySQL InnoDB锁机制 - 教程

2025-09-26 20:07  tlnshuju  阅读(50)  评论(0)    收藏  举报

这段文档是 MySQL InnoDB 存储引擎中关于锁机制(InnoDB Locking)的权威详解,内容非常核心和深入。它解释了 InnoDB 如何通过多种类型的锁来实现并发控制、数据一致性、避免幻读与死锁

我们将用通俗语言 + 图解思维 + 实际例子,帮你彻底理解每一个锁类型及其作用。


总体目标:InnoDB 为什么要搞这么多锁?

  • 多个事务同时操作数据库时,必须防止:
    • 数据被乱改(丢失更新)
    • 读到不一致的数据(脏读、不可重复读、幻读)
    • 死锁导致系统卡住
  • 所以 InnoDB 设计了一套精细的锁定机制,在性能一致性之间取得平衡。

一、两大基础锁:共享锁(S)和排他锁(X)

这是所有锁的“起点”。

锁类型名称能做什么?
S 锁Shared Lock(共享锁 / 读锁)持有者可以读数据
X 锁Exclusive Lock(排他锁 / 写锁)持有者可以修改或删除数据

✅ 规则:

  1. 多个事务可以同时持有 S 锁 → 多人可读
  2. 只要有 X 锁存在,别人就不能加任何锁(S 或 X)
  3. 只要有 S 锁存在,别人可以加 S 锁,但不能加 X 锁

类比:图书馆里一本书

  • 多人可以同时看书(S 锁)✅
  • 只有一个人能借走修书(X 锁)❌其他人不能看也不能借

哪些语句会加这些锁?

SQL 语句加的锁
SELECT ...不加锁(MVCC 读历史版本)
SELECT ... FOR SHARES 锁
SELECT ... FOR UPDATEX 锁
UPDATE, DELETE自动加 X 锁
INSERT插入前加 insert intention lock(见下文)

️ 二、意向锁(Intention Locks)—— 表级“预告”锁

❓ 问题来了:

如果事务 A 正在修改某一行(加了 X 锁),这时事务 B 想对整张表加锁(如 LOCK TABLES t WRITE),怎么办?

→ 必须知道“有没有人正在用这表里的行”!

但逐行检查太慢了!于是有了 意向锁(Intention Locks)

✅ 意向锁的作用:

  • 表级别的锁
  • 只表示:“我打算在某个行上加 S 或 X 锁”
  • 让表级锁和行级锁能高效共存
意向锁含义
IS意向共享锁:我要给某些行加 S 锁
IX意向排他锁:我要给某些行加 X 锁

✅ 使用规则:

  • 要加 S 锁 → 先获得 IS 或更强的锁(如 IX)
  • 要加 X 锁 → 必须先获得 IX 锁

意向锁兼容性矩阵(重点!)

XIXSIS
X
IX
S
IS

✅ = 可以共存,❌ = 冲突

关键点

  • 意向锁之间大多兼容(IS 和 IX 可共存)
  • 真正冲突的是 表级读写锁(S/X)之间的互斥
  • 意向锁不阻塞任何行操作,只用于快速判断是否可以执行表锁

示例:

-- 事务A
START TRANSACTION;
SELECT * FROM t WHERE id=1 FOR UPDATE;
-- 加 X 锁 → 自动在表上加 IX 锁
-- 此时另一个事务想 LOCK TABLE t READ(S 锁)?
-- 会检查:IX 和 S 冲突吗?→ ❌ 冲突!所以等待。

三、行锁的具体实现:Record Lock、Gap Lock、Next-Key Lock

1. Record Lock(记录锁)

  • 锁住某个索引记录本身
  • 最基本的行锁

✅ 示例:

SELECT * FROM t WHERE id=10 FOR UPDATE;
-- 对 id=10 的索引记录加 X 锁(record lock)

⚠️ 即使表没有主键,InnoDB 也会创建隐藏的聚簇索引,所以总是有“索引记录”可锁。


2. Gap Lock(间隙锁)

  • 锁住两个索引值之间的“空隙”
  • 目的:防止其他事务在这个“空隙”插入新记录 → 防止幻读

✅ 示例:

SELECT * FROM t WHERE age BETWEEN 20 AND 30 FOR UPDATE;
-- 不仅锁住 age=20~30 的记录
-- 还锁住 (20,30) 这个“范围”,别人不能插入 age=25 的记录

关键特性:

  • Gap Lock 不互斥!
    • 事务 A 可以在 gap 上加 S-gap 锁
    • 事务 B 也可以在同一 gap 加 X-gap 锁
  • 因为 gap 锁只是“防止插入”,不是保护数据内容
  • 当记录被删除时,多个 gap 锁需要合并,所以允许共存

何时不用 Gap Lock?

  • 查询条件使用唯一索引且命中唯一行时
    SELECT * FROM t WHERE id = 100 FOR UPDATE;
    -- id 是 PRIMARY KEY
    -- 只加 record lock,不加 gap lock
    因为已经确定只有一行,不可能出现“幻影”

3. Next-Key Lock = Record Lock + Gap Lock

  • 锁住记录本身 + 前面的间隙
  • 是 InnoDB 在 REPEATABLE READ 隔离级别下的默认锁机制
  • 目的:完全防止幻读

✅ 示例:
假设索引有值:10, 11, 13, 20

Next-Key Lock 覆盖的区间是:

(-∞, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +∞)

每个区间都是左开右闭:(a, b] 表示不包含 a,包含 b

特别说明最后一个区间 (20, +∞)

  • 实际上是一个叫 supremum 的虚拟记录
  • 锁住“最大值之后的所有空间”

效果:

  • 任何想插入 age=25 的操作都会被阻塞
  • 实现了“可重复读”下的范围查询一致性

四、Insert Intention Lock(插入意向锁)

❓ 问题:

多个事务都想往同一个“间隙”插入数据,但位置不同,应该阻塞吗?

比如:

  • 事务 A 想插 id=5
  • 事务 B 想插 id=6
  • 当前只有 id=4id=7

→ 他们其实不冲突,应该允许并发插入!

✅ Insert Intention Lock 就是为此设计的:

  • 是一种特殊的 Gap Lock
  • 表示:“我打算在这个 gap 里插入一条记录”
  • 多个事务可以在同一个 gap 上设置 insert intention lock,只要插入位置不同,就不冲突

工作流程:

  1. 事务要插入 → 先在 gap 上加 insert intention lock
  2. 然后尝试获取该行的 X 锁(record lock)
  3. 如果 gap 被别人用 X 锁锁住了(比如 FOR UPDATE 范围查询),则等待

示例:

-- 表中有 id=4 和 id=7
-- 事务A
INSERT INTO t(id) VALUES (5);
-- 在 (4,7) 加 insert intention lock → 成功
-- 事务B
INSERT INTO t(id) VALUES (6);
-- 同样在 (4,7) 加 insert intention lock → 也成功!
-- 因为 5≠6,不冲突

但如果:

-- 事务C
SELECT * FROM t WHERE id BETWEEN 4 AND 7 FOR UPDATE;
-- 加了 next-key lock,锁住 (4,7] 范围
-- 那么事务A/B 插入就会被阻塞

五、AUTO-INC Lock(自增锁)

❓ 问题:

多个事务同时 INSERT 到有 AUTO_INCREMENT 列的表,怎么保证 ID 不重复且连续?

✅ AUTO-INC Lock 是一种表级锁,用于保护自增值分配。

情况是否阻塞
一个事务在插入 → 其他事务插入必须等待✅ 默认行为
使用 innodb_autoinc_lock_mode=2(交错模式)❌ 不阻塞,但 ID 可能不连续

⚙️ 参数控制:

  • innodb_autoinc_lock_mode
    • 0: 传统模式(最安全,性能差)
    • 1: 默认模式(平衡)
    • 2: 高并发模式(最快,ID 可能跳跃)

现代 MySQL 推荐使用 2,除非你需要严格连续的 ID。


六、Predicate Locks for Spatial Indexes(空间索引的谓词锁)

❓ 特殊情况:空间数据(如经纬度、地理区域)

  • 没有“大小顺序”,无法用 next-key locking
  • 传统锁机制失效

✅ 解决方案:谓词锁(Predicate Lock)

  • 锁住一个“查询条件”本身
  • 例如:WHERE MBRContains(shape, Point(1,2))
  • InnoDB 会对这个 MBR(最小包围矩形)加锁
  • 其他事务不能插入或修改满足该条件的行

这是一种更高级的逻辑锁,确保空间查询的一致性。


✅ 总结:一张表看懂所有锁

锁类型级别用途是否阻塞其他操作
S/X Lock行级读/写锁定具体行是(X 锁最强)
IS/IX表级预告要加行锁不阻塞行操作
Record Lock行级锁具体索引记录阻塞对该记录的修改
Gap Lock行级锁间隙,防插入阻塞插入该范围
Next-Key Lock行级record + gap,防幻读强一致性保障
Insert Intention行级插入前声明意图不同位置可并发
AUTO-INC表级保护自增列可能阻塞并发插入
Predicate Lock逻辑级空间查询一致性特殊场景使用

实际建议

场景建议
普通查询SELECT(MVCC,无锁)
先查后改SELECT ... FOR UPDATE
防止幻读REPEATABLE READ + 范围查询自动加 next-key lock
高并发插入设置 innodb_autoinc_lock_mode=2
减少死锁所有事务按相同顺序操作表
查锁信息SHOW ENGINE INNODB STATUS

一句话总结

InnoDB 通过 S/X 锁 + 意向锁 + 记录锁 + 间隙锁 + 插入意向锁 的组合,在行级精细控制并发,既保证数据一致性(防止脏读、幻读),又尽可能提升并发性能。理解这些锁,是掌握 MySQL 高并发能力的关键。