【MySQL修炼篇】从S锁/X锁到Next-Key Lock:MySQL锁机制硬核拆解

【MySQL修炼篇】从 S 锁 / X 锁 到 Next-Key Lock:MySQL 锁机制硬核拆解

MySQL(InnoDB 引擎)的锁机制是面试和生产环境中最常被深挖的知识点之一。
很多同学能背出“间隙锁、Next-Key Lock、幻读”,但一问具体场景、加锁范围、死锁成因,就卡壳了。

这篇我们从最基础的读锁 / 写锁开始,一层一层拆到最核心的 Next-Key LockGap LockRecord Lock,并结合实际 SQL 语句说明到底锁住了哪些范围

1. 基础概念:共享锁(S锁)与排他锁(X锁)

锁类型英文兼容性加锁时机典型场景是否阻塞读写
S 锁Shared Lock与 S 锁兼容,与 X 锁互斥普通 SELECT … FOR SHARE / LOCK IN SHARE MODE读已提交的记录,想防止别人修改阻塞写
X 锁Exclusive Lock与任何锁都互斥INSERT / UPDATE / DELETE / SELECT … FOR UPDATE修改记录、删除记录、加行锁的查询阻塞读写

一句话总结

  • S 锁:大家都能读,但不能改(读锁)
  • X 锁:只有一个人能读能改,其他人全阻塞(写锁)

2. InnoDB 锁的粒度(从粗到细)

InnoDB 支持三种主要锁粒度:

  1. 表锁(Table Lock)
  • 语法:LOCK TABLES t READ / WRITE
  • 很少用,基本被行锁取代
  1. 意向锁(Intention Lock)
  • IS 锁(意向共享锁):我要加 S 锁
  • IX 锁(意向排他锁):我要加 X 锁
  • 作用:表级兼容检查,加速判断是否能加表锁
  • 几乎都是自动加的,程序员不用管
  1. 行锁(Row Lock)—— InnoDB 锁机制的核心
    这是我们今天要重点拆解的部分

3. InnoDB 行锁的三种基本类型

锁名锁住什么锁范围示意(假设 id 是主键)是否防止幻读加锁时机备注
Record Lock精确的单条记录只锁 id = 5 这一行唯一索引精确命中时最小的锁
Gap Lock记录之间的“间隙”锁 (3,5)、(5,8) 两个间隙非唯一索引范围查询、重复读隔离级别防止幻读关键
Next-Key LockRecord + Gap锁 id=5 这行 + (3,5) + (5,8)范围查询(>、<、>=、<=、between、like ‘abc%’)默认锁

一句话记忆

  • Record Lock:只锁点(=)
  • Gap Lock:只锁缝(间隙)
  • Next-Key Lock:点 + 缝一起锁(最常见)

4. 不同隔离级别下锁的行为(重点!)

隔离级别默认锁类型(范围查询)是否有 Gap Lock是否防止幻读常见加锁场景示例(SELECT * FROM t WHERE id > 5 FOR UPDATE)
Read Uncommitted无锁基本不加锁(脏读)
Read CommittedRecord Lock只锁命中的记录,间隙不锁(可能幻读)
Repeatable ReadNext-Key Lock锁记录 + 间隙(MySQL 默认隔离级别,防幻读)
Serializable表级共享锁(读)所有读都加锁,性能极差(基本不用)

结论
MySQL 默认隔离级别(RR) + 范围查询 + 加写锁(FOR UPDATE) → 几乎总是 Next-Key Lock

5. 真实 SQL 加锁范围举例(最容易考)

假设表结构:

CREATE TABLE t (
  id INT PRIMARY KEY,
  key1 INT,
  key2 VARCHAR(20),
  INDEX idx_key1 (key1),
  INDEX idx_key2 (key2)
);

当前数据:

id | key1 | key2
---+------+------
3  | 10   | abc
5  | 20   | bcd
8  | 30   | cde
11 | 30   | def

案例 1:主键精确匹配

SELECT * FROM t WHERE id = 5 FOR UPDATE;

只加 Record Lock,锁住 id=5 这一行

案例 2:主键范围查询

SELECT * FROM t WHERE id > 5 AND id < 10 FOR UPDATE;

Next-Key Lock
锁范围:(5,8] 即锁住 id=8 这行 + (5,8) 间隙

案例 3:非唯一索引等值查询

SELECT * FROM t WHERE key1 = 30 FOR UPDATE;

Next-Key Lock
因为 key1=30 有两条记录(id=8 和 id=11),会锁:

  • id=8 这行 + (5,8) 间隙
  • id=11 这行 + (8,11) 间隙

案例 4:非唯一索引范围查询(最经典幻读场景)

SELECT * FROM t WHERE key1 > 15 AND key1 < 25 FOR UPDATE;

Next-Key Lock
锁范围:(10,20] + (20,30]
→ 锁住 id=20 这行 + (10,20) 间隙 + (20,30) 间隙
→ 防止别人在 (10,20) 或 (20,30) 插入新记录(防幻读)

案例 5:唯一索引范围查询 + 等值

SELECT * FROM t WHERE id >= 5 AND id <= 8 FOR UPDATE;

Next-Key Lock(左闭右闭)
锁:id=5、id=8 两行 + (3,5)、(5,8)、(8,11) 三个间隙

6. 死锁与间隙锁的典型案例

最经典死锁场景(两个事务交叉锁间隙):

事务 A:

UPDATE t SET key2='xxx' WHERE key1=30;   -- 锁 (8,11] + id=11

事务 B:

INSERT INTO t (id,key1,key2) VALUES (9,30,'new');  -- 想插入到 (8,11) 间隙,被 A 挡住

事务 A 再执行:

UPDATE t SET key2='yyy' WHERE key1=30;   -- 想锁 id=8,被 B 挡住

死锁(A 等 B,B 等 A)

解决思路

  • 降低隔离级别到 RC(但会丢失幻读保护)
  • 尽量用主键或唯一索引定位
  • 批量操作时排序一致
  • 缩短事务范围

7. 总结:MySQL 锁的“口诀”

  1. RR 隔离级别 + 范围 + FOR UPDATE → 基本都是 Next-Key Lock(锁记录 + 锁间隙)
  2. 唯一索引精确命中 → 退化为 Record Lock(只锁点)
  3. 非唯一索引或范围 → Next-Key Lock(锁点 + 锁间隙)
  4. 间隙锁只在 RR 隔离级别存在,RC 隔离级别没有
  5. 幻读问题本质:防止在查询范围内的“新增”或“删除”导致前后两次读不一致
  6. 死锁 80% 与间隙锁有关,优化方向:用主键、缩短事务、顺序访问

如果你正在准备面试或优化线上慢查询/死锁,可以告诉我具体场景(比如“SELECT … FOR UPDATE 导致死锁”或“RR 隔离下还能幻读吗”),我可以继续给你更细的案例和 SQL 演示。

文章已创建 4357

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部