UPDATE 语句到底锁了什么?MySQL InnoDB 加锁机制实战解析

日常业务开发中,UPDATE 语句是频繁使用的操作之一。然而一个令人困惑的问题是:一条 UPDATE 语句在执行时,底层到底加锁了数据库中的哪些范围?

这个问题在网上讨论多年,众说纷纭:

  • 有人说是行锁(Record Lock)
  • 有人说是间隙锁(Gap Lock)
  • 有人说是Next-Key Lock(前开后闭,记录锁 + 间隙锁的组合)

各有说法,公说公有理,婆说婆有理,争论不休。

其实这个问题并不复杂,比起抽象晦涩的名词解释,不如我们通过一次清晰的实验,以“看得见、摸得着”的方式去亲身体验 InnoDB 锁定的真正范围。数据不会说谎,结果最有说服力。


一、实验目标:弄清楚 UPDATE 的加锁范围

我们将通过构造一张简单的数据表、执行不同类型的 UPDATE 操作,并配合验证性 INSERT 语句,来逐个分析:

  • 精准命中行是否加锁?
  • 更新不存在记录是否加锁?加哪一段?
  • 主键之间的“间隙”是否被锁?
  • 范围更新加锁范围是大是小?
  • InnoDB 加锁策略到底依据什么?

二、实验环境配置

为确保大家可以复现本实验,这里提供完整环境参数:

  • 数据库版本:MySQL 8.0.27
  • 存储引擎:InnoDB(支持多种行级锁)
  • 事务隔离级别:RR(Repeatable Read,可重复读,InnoDB 默认)
  • 数据表结构:仅包含两个字段(主键+name)
CREATE TABLE test_lock (
  id INT PRIMARY KEY,
  name VARCHAR(100)
);

初始化数据:

INSERT INTO test_lock (id, name) VALUES (10, 'a'), (50, 'b');

当前表中主键 ID 仅包含两个值:1050


三、实验设计与 SQL 概览

我们设计了四组基础 UPDATE 语句,对应四种典型场景,分别命名为 M1 ~ M4

编号SQL 语句场景说明
M1UPDATE test_lock SET name = 'x' WHERE id = 7非精准匹配,目标记录不存在
M2UPDATE test_lock SET name = 'x' WHERE id = 10精准匹配,目标记录存在
M3UPDATE test_lock SET name = 'x' WHERE id = 12非精准匹配,夹在现有主键之间
M4UPDATE test_lock SET name = 'x' WHERE id >= 8 AND id <= 11范围更新,部分存在,部分不存在

四、实验验证方式:双窗口并发测试锁行为

我们使用两条并发事务:

  • 事务A(主事务):执行 UPDATE 语句,不提交也不回滚,保持锁定
  • 事务B(验证事务):执行不同的 INSERT 语句,尝试插入不同主键值,观察是否被阻塞

核心判断标准:

  • 如果插入被阻塞,说明该 ID 所在位置被锁住;
  • 如果插入立刻成功,说明该位置未被锁定。

五、四组 UPDATE 实验解析

M1:UPDATE test_lock SET name = 'x' WHERE id = 7

  • 当前表中并无 ID = 7 的记录;
  • 该值小于现有的 10,因此锁定了 (负无穷, 10) 区间的间隙。
实验现象:
插入 ID是否阻塞原因
1 ~ 9阻塞位于 (−∞,10) 区间内,被间隙锁锁住
10报主键冲突行已存在
>10正常插入不在锁定范围
加锁类型:
  • Gap Lock(间隙锁)
  • 锁定范围:(−∞,10),不包含10本身

M2:UPDATE test_lock SET name = 'x' WHERE id = 10

  • 命中现有记录;
  • 仅对 id = 10 这行加锁。
实验现象:
插入 ID是否阻塞原因
10主键冲突已存在
其他任何值正常插入无锁冲突
加锁类型:
  • Record Lock(记录锁)
  • 锁定范围:仅限于 id=10 本身

M3:UPDATE test_lock SET name = 'x' WHERE id = 12

  • 表中没有 ID = 12,但该值位于两个已存在主键(10 与 50)之间;
  • 加锁策略是锁定该值所在的索引“间隙”。
实验现象:
插入 ID是否阻塞
11、13、14、…、49阻塞(间隙锁)
10、50不阻塞(节点存在,不在间隙中)
≥51正常插入
加锁类型:
  • Gap Lock
  • 锁定范围:(10,50)

M4:UPDATE test_lock SET name = 'x' WHERE id >= 8 AND id <= 11

  • 范围包含 id=10,其余未命中;

  • 会加:

    • 记录锁:锁住 id=10
    • 间隙锁:锁住范围内不存在记录的间隙,如 (8,10)(10,11)
实验现象:
插入 ID是否阻塞原因
7正常不在查询范围内
8阻塞在区间起始间隙内
9、11阻塞在间隙中
10主键冲突行已存在
12+正常不在锁定区间
加锁类型:
  • Next-Key Lock(记录锁 + 间隙锁)
  • 锁定范围:(−∞,10)id=10(10,11)

六、InnoDB 加锁机制总结

场景SQL 示例加锁类型锁定范围
精准匹配命中UPDATE ... WHERE id=10记录锁只锁命中行
精准匹配未命中UPDATE ... WHERE id=7间隙锁锁住目标值所处间隙
中间值未命中UPDATE ... WHERE id=12间隙锁锁住 12 所在索引间隙 (10,50)
范围更新UPDATE ... WHERE id>=8 AND id<=11Next-Key Lock记录锁 + 所有相关间隙

七、加锁原理:B+ 树索引结构的影响

InnoDB 中的主键索引是 B+ 树结构:

  • 每个节点按主键顺序排列
  • 叶子节点之间是双向链表
  • 间隙锁(Gap Lock)实际上锁的是 相邻叶子节点之间的区间
  • Next-Key Lock 是对记录本身和左右间隙一起加锁

图示:

现有主键为:1050

        B+ 树索引结构:
  ---|---(10)---|---(50)---|---
  • id = 7 锁的是 (−∞,10)
  • id = 12 锁的是 (10,50)
  • id = 10 锁的是 [10]
  • 范围 id >= 8 and id <= 11 锁的是 (−∞,10) + [10] + (10,11)

八、实际开发中的建议

  1. 尽量使用精准的主键更新

    • 锁粒度最小,性能最好
  2. 避免非命中更新

    • 无谓加锁间隙,影响插入并发性
  3. 范围更新慎用

    • 特别是热点表上,Next-Key Lock 会锁较大范围
  4. 合理设计主键值

    • 稀疏、均匀分布的主键可以减少间隙锁冲突
  5. 并发插入场景中避免同时进行范围更新


九、实验脚本与复现建议

你可以使用以下步骤手动验证上述实验:

步骤一:开启事务窗口A

START TRANSACTION;
UPDATE test_lock SET name = 'x' WHERE id = 7; -- 不提交

步骤二:在窗口B执行 INSERT 验证是否阻塞

INSERT INTO test_lock (id, name) VALUES (9, 'test'); -- 会阻塞

通过这种方式,你可以自行验证各种 UPDATE 的加锁范围,理解最清晰!


十、结语

通过一组清晰的实验,我们最终得出了结论:

  • 不同 UPDATE 写法对加锁范围影响巨大
  • InnoDB 加锁策略与 B+ 树结构密切相关
  • 合理使用索引与写法,是保证高并发性能的关键

记住这三句话

  1. 命中即行锁,精准高效;
  2. 不命中即间隙锁,小心误伤;
  3. 范围查询慎用,锁范围可能远超想象。

希望本文实验+原理的方式能帮助你彻底搞懂 InnoDB 的锁机制。

如果你已经看懂,不妨照着实验动手试一遍。数据不会骗人,实践最能加深记忆。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值