MySQL 的行级锁到底是怎么加的?
开篇结论
在深入探讨之前,我们先明确几个核心概念。数据库加锁的对象是索引,而加锁的基本单位是 next-key lock。它本质上是一个组合体,由记录锁(锁定索引记录本身)和间隙锁(锁定记录之间的间隙)共同构成。一个关键的区别在于区间:next-key lock 是左开右闭区间,而间隙锁则是左开右开区间。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
不过,规则并非一成不变。在某些场景下,如果仅使用记录锁或间隙锁就足以防止“幻读”现象,那么 next-key lock 就会“退化”,变成更精确的记录锁或间隙锁。理解这些退化规则,是掌握 InnoDB 锁机制的关键。
为了便于后续分析,我们假设有这样一张用户表:id 是主键(唯一索引),age 是普通索引(非唯一索引),name 是普通列。表中的初始数据如下图所示:

一、唯一索引等值查询
当使用唯一索引进行等值查询时,加锁行为会根据查询的记录是否存在而发生有趣的变化。
简单来说,规则是这样的:如果查询的记录存在,在索引树上精准定位到它之后,原本的 next-key lock 会退化成单纯的记录锁。反之,如果查询的记录不存在,引擎会找到第一条大于该查询值的记录,然后将其上的 next-key lock 退化成间隙锁。
1. 记录存在的情况
来看一个具体的例子。假设事务 A 执行了下面这条查询,并且 id=1 的记录确实存在于表中。
select * from user where id = 1 for update;
那么,事务 A 会为 id=1 的这条记录加上 X 型(排他)的记录锁。

这样一来,任何其他事务试图更新或删除这条 id=1 的记录,都会被阻塞。你可能会问,为什么这里会退化成记录锁?道理其实很直接:在这个场景下,仅靠记录锁就足以防止幻读。因为其他事务既无法修改也无法插入一条新的 id=1 的记录(唯一键冲突),前后两次查询的结果集自然不可能发生变化。
2. 记录不存在的情况
现在,换个条件。假设事务 A 查询一条不存在的记录。
select * from user where id = 2 for update;
这时,事务 A 会在 id=5 这条记录的主键索引上加上一个间隙锁,锁定的范围是 (1, 5)。

这意味着,其他事务如果想插入 id 为 2、3 或 4 的新记录,都会被阻塞。那么,为什么这里退化成间隙锁就足够了呢?试想,如果这里加的是 next-key lock(即 (1,5]),那么 id=5 这条记录本身也会被锁住,导致它无法被更新或删除。但实际上,无论 id=5 这条记录是否被修改,事务 A 的查询结果(空集)都不会改变。因此,一个只防止插入的间隙锁,恰恰是性价比最高的选择。
二、唯一索引范围查询
范围查询的加锁逻辑要稍微复杂一些。基本原则是:对于扫描到的每一条索引记录,都会先加上 next-key lock。但在某些特定条件下,这些锁会发生退化。
具体规则可以归纳为两点:
- 针对大于等于的范围查询,由于包含了等值查询的条件,如果等值查询的记录存在,那么该记录上的 next-key lock 会退化为记录锁。
- 针对小于或小于等于的范围查询,情况取决于条件值对应的记录是否存在:
- 如果该记录不存在,那么扫描到终止查询的那条记录时,其 next-key lock 会退化为间隙锁。
- 如果该记录存在,对于“小于”查询,终止记录的 next-key lock 退化为间隙锁;对于“小于等于”查询,则不会退化。
1. 针对大于或者大于等于的范围查询
(1) 针对大于的范围查询的情况
假设事务 A 执行:
select * from user where id > 15 for update;
此时,事务 A 会在主键索引上施加两个 next-key lock,如下图所示:

- 在 id=20 的记录上,加了范围为 (15, 20] 的 next-key lock。这既防止了 id=20 被修改或删除,也阻止了 id 为 16到19 的新记录插入。
- 在特殊的“上确界”记录上,加了范围为 (20, +∞] 的 next-key lock,防止插入任何 id 大于20的新记录。
这里所有的锁都没有退化。原因很明确:要保证“id>15”这个条件的结果集稳定,必须同时防止范围内的记录被修改(记录锁)和新记录插入(间隙锁),next-key lock 正好满足这个需求。
(2) 针对大于等于的范围查询的情况
假设事务 A 执行:
select * from user where id >= 15 for update;
这时,事务 A 会加上三把锁:

- 在 id=15 的记录上,因为等值查询命中,next-key lock 退化为记录锁,仅锁定这一行。
- 在 id=20 的记录上,加 (15, 20] 的 next-key lock。
- 在“上确界”记录上,加 (20, +∞] 的 next-key lock。
看到了吗?id=15 上的锁退化了,其逻辑与之前“唯一索引等值查询”的情况完全一致。
2. 针对小于或者小于等于的范围查询
(1) 查询条件值记录不存在于表中的情况
假设查询 id < 6,且 id=6 这条记录不存在。
select * from user where id < 6 for update;
加锁情况如下:

- 在 id=1 上加 (-∞, 1] 的 next-key lock。
- 在 id=5 上加 (1, 5] 的 next-key lock。
- 在 id=10 上加 (5, 10) 的间隙锁。注意,这里扫描到 id=10(第一条不满足条件的记录)时,next-key lock 退化了。
这正是前面规则的体现:当条件值记录不存在时,扫描到终止记录,其 next-key lock 退化为间隙锁。
(2) 小于等于查询,且条件值记录存在的情况
假设查询 id <= 5,且 id=5 存在。
select * from user where id <= 5 for update;
加锁情况如下:

- 在 id=1 上加 (-∞, 1] 的 next-key lock。
- 在 id=5 上加 (1, 5] 的 next-key lock。
这里 id=5 上的锁没有退化。为什么?对于“id<=5”的查询,id=5 这条记录本身就在结果集中。如果这里退化成间隙锁,其他事务就能删除 id=5 这条记录,导致前后两次查询结果集不同,引发幻读。因此,必须用 next-key lock 将其锁住。
(3) 小于查询,且条件值记录存在的情况
假设查询 id < 10,且 id=10 存在(但不满足条件)。
select * from user where id < 10 for update;
加锁情况与第(1)种情况完全一致:

- 在 id=1 上加 (-∞, 1] 的 next-key lock。
- 在 id=5 上加 (1, 5] 的 next-key lock。
- 在 id=10 上加 (5, 10) 的间隙锁。
因为 id=10 是第一条不满足条件的记录,且查询条件是“小于”,所以其 next-key lock 退化为间隙锁。
三、非唯一索引等值查询
非唯一索引的等值查询更为复杂,因为它涉及两个索引:主键索引和二级索引。加锁时会对两者都加锁,但对主键索引加锁仅限于那些满足查询条件的记录。
核心规则同样分两种情况:
- 记录存在:由于非唯一性,可能存在多条值相同的记录。因此,查询过程是一个扫描过程,直到找到第一条不满足条件的记录为止。扫描过程中,对所有扫描到的二级索引记录加 next-key lock,而对第一条不满足条件的记录,其 next-key lock 会退化成间隙锁。同时,为所有满足条件记录的主键索引加记录锁。
- 记录不存在:扫描到第一条不满足条件的记录后,将其 next-key lock 退化成间隙锁。由于没有满足条件的记录,因此不会对任何主键索引加锁。
1. 记录不存在的情况
假设事务 A 查询 age = 25(表中不存在此记录)。
select * from user where age = 25 for update;
引擎会扫描到第一条 age > 25 的记录,即 age=39。于是,在这条记录的二级索引上,next-key lock 退化为间隙锁,范围是 (22, 39)。

这意味着,插入 age 值在 23 到 38 之间的新记录都会被阻塞。这个间隙锁的目的,正是为了防止幻读——避免在查询期间有 age=25 的记录被插入。
但这里有个细节值得玩味:对于插入 age=22 或 age=39 的语句,是否被阻塞取决于插入记录的主键 id 值。因为二级索引的定位是“索引值+主键值”。插入操作能否成功,关键看插入位置的下一条记录是否被加了间隙锁。
- 插入 age=22:如果插入 id=3, age=22,其下一条记录是 (id=10, age=22),该记录未被加锁,可以插入。如果插入 id=12, age=22,其下一条记录是 (id=20, age=39),该记录被加了间隙锁,插入被阻塞。
- 插入 age=39:如果插入 id=3, age=39,其下一条记录是 (id=20, age=39),被加锁,插入阻塞。如果插入 id=21, age=39,其下一条记录不存在,无锁,可以插入。
所以说,间隙锁锁住的是一个“区间范围”,但插入操作是否被阻塞,还要看它具体想插入到哪个“位置”。
2. 记录存在的情况
假设事务 A 查询 age = 22(表中存在多条记录)。
select * from user where age = 22 for update;
加锁过程是一个扫描过程:
- 找到第一条 age=22 的记录 (id=10),对其二级索引加 (21, 22] 的 next-key lock,并对主键索引 id=10 加记录锁。
- 继续扫描,找到下一条 age=39 的记录,这是第一个不满足条件的记录,将其二级索引上的 next-key lock 退化为间隙锁 (22, 39)。
- 停止扫描。
最终加锁情况如下图所示:

- 主键索引:仅对 id=10 加了记录锁。
- 二级索引:
- 在 age=22 的记录上,加了 (21, 22] 的 next-key lock。
- 在 age=39 的记录上,加了 (22, 39) 的间隙锁。
这些锁共同作用,完美避免了幻读:记录锁保护了已存在的 age=22 记录不被修改;两个二级索引锁则防止了在查询范围内插入新的 age=22 的记录。
同样地,插入 age=21 或 22 的记录是否成功,也取决于其主键 id 值,原理与前述相同。
四、非唯一索引范围查询
非唯一索引的范围查询,规则反而简单了:next-key lock 不会发生退化。也就是说,对扫描到的每一条二级索引记录,加的都是 next-key lock。
以这条查询为例:
select * from user where age >= 22 for update;
加锁情况如下:

- 主键索引:对 id=10 和 id=20 这两条满足条件的记录加记录锁。
- 二级索引:
- age=22: 加 (21, 22] 的 next-key lock。
- age=39: 加 (22, 39] 的 next-key lock。
- 上确界记录: 加 (39, +∞] 的 next-key lock。
这里有一个关键问题:为什么 age=22 这个等值查询命中的记录,其 next-key lock 没有像唯一索引那样退化为记录锁?根本原因在于 age 字段的非唯一性。如果只加记录锁,它只能防止这条记录被删除或修改,但无法阻止其他事务插入一条全新的 age=22 的记录。这样一来,前后两次范围查询的结果集就可能不同,幻读就此产生。因此,必须使用 next-key lock 来同时防止插入。
五、没有加索引的查询
最后,我们谈谈最需要警惕的一种情况。如果一条锁定读查询(如 SELECT ... FOR UPDATE)没有使用索引列作为条件,或者没有走索引扫描,导致全表扫描,那么后果会很严重:每一条记录的索引上都会被加上 next-key lock。这实质上相当于锁住了整个表。
需要特别注意的是,不仅仅是 SELECT ... FOR UPDATE,UPDATE 和 DELETE 语句如果条件不走索引,同样会引发全表扫描和全表加锁。
因此,在线上环境中,执行任何带有加锁性质的语句时,务必检查其执行计划,确保它使用了合适的索引。全表扫描带来的锁表问题,对数据库并发性能的影响是致命的。当然,即使带了索引,也可能因为优化器选择而走全表扫描,但不带索引则必定是全表扫描,这一点必须牢记。
热门专题
热门推荐
红色沙漠无限爆炸弓箭流终极攻略:零消耗箭矢打造移动炮台 你是否渴望在《红色沙漠》中化身为人形自走炮台,享受无与伦比的清屏快感?无限爆炸弓箭流正是实现这一梦想的顶级玩法。其核心精髓在于彻底颠覆常规弹药限制,将珍贵的爆炸箭转化为取之不尽、用之不竭的无限火力,让玩家体验到“坐轮椅”般轻松碾压一切的爽快战斗
Adsby是什么 提到AI广告优化,很多人的第一反应是复杂和昂贵。但有一款工具正在改变这个局面,它就是Adsby。简单来说,Adsby是一个专为初创公司和中小企业量身打造的智能广告助手。它的使命很明确:把专业级的数字广告优化能力,通过AI自动化,变得简单、高效且负担得起。核心聚焦于Google Ad
AI ASO Manager: Hire a pro for $15 是什么 在应用商店的激烈战场上,想用一杯咖啡的价格请到一位优化专家?这事儿还真有。AI ASO Manager: Hire a pro for $15,就是由Creati ai推出的一款智能工具,它的核心任务非常明确:帮你搞定Go
红色沙漠野狼追踪者头盔获取指南 许多《红色沙漠》的玩家都在寻找野狼追踪者头盔的获取方法。这件带有生物追踪功能的特殊头部装备,对于喜欢探索开放世界和进行狩猎的玩家来说,是一件极具价值的实用道具。好消息是,它的获取途径非常直接,不需要完成复杂任务或挑战强力敌人。 红色沙漠野狼追踪者头盔如何获得 成功获取
App & API Privacy Mgmt是什么 今天,如果你和软件开发者、数据隐私专家或者企业安全团队聊聊,他们十有八九会提到一个共同的痛点:如何在复杂的应用和API交互中,确保海量数据的安全与合规。这可不是个小工程,手动审查效率低下,而一旦出问题,代价往往极其高昂。正是在这个背景下,由APIP





