InnoDB 加锁到底发生在索引还是行?
数据库面试中,有一个高频却容易答错的问题:
InnoDB 的加锁到底是锁“行”还是锁“索引”?
很多同学会回答:锁的是行。
但实际上——
InnoDB 的锁是加在索引上的,不是行上的!
如果表没有索引,加锁范围会变得不可控甚至锁全表。
为什么?
InnoDB 到底锁了什么?
为什么范围查询会锁一大片?
为什么没索引会死锁频发?
一、结论:InnoDB 的锁是加在索引上的,而不是数据行
你必须先记住一句话:
InnoDB 的所有行锁,本质上都是“索引项锁”。
如果没有可用索引,InnoDB 会进行全表扫描,并对所有扫描到的数据加锁。
也就是说:
-
• 查询命中了索引 → 锁的是索引项 -
• 查询没命中索引 → 会锁住全表对应所有索引项(相当于锁全表)
所以 InnoDB 并不存在“按行锁数据页”这种操作,而是通过“索引结构”来定位数据行并加锁。
二、为什么锁是加在索引上的?
核心原因:InnoDB 是 索引组织表 (Index Organized Table, IOT)
InnoDB 的数据本身就是按照主键索引(B+Tree)存储的。
也就是说:
-
• 主键索引叶子节点保存了数据行 -
• 其他二级索引叶子节点保存的是主键
因此:
❗要锁住一行数据,必须先访问索引。
因为:
-
• 查记录 → 必须访问索引 -
• 更新记录 → 必须访问主键索引 -
• 删除记录 → 必须定位到主键索引
👉 所以所有锁操作都以“索引项”为单位进行。
三、不同类型的锁到底锁在哪里?
InnoDB 行锁(索引锁)包括:
-
• 记录锁(Record Lock):锁定某条索引记录 -
• 间隙锁(Gap Lock):锁定两个索引值之间的间隙 -
• 临键锁(Next-Key Lock):Record Lock + Gap Lock -
• 插入意向锁(Insert Intention Lock)
无论哪种锁,都是锁 索引项或索引间隙。
下面通过例子展示。
四、例子:使用主键索引的加锁方式
表结构:
CREATE TABLE user (
id INT PRIMARY KEY,
age INT,
name VARCHAR(20),
KEY idx_age(age)
) ENGINE=InnoDB;
SQL:
SELECT * FROM user WHERE id = 10 FOR UPDATE;
锁的位置:
-
• 锁的是主键索引 B+Tree 上 id = 10 这一条索引项 -
• 并不会锁 name 字段所在的“行” -
• 加锁时不访问二级索引
五、例子:使用二级索引的加锁方式
SQL:
SELECT * FROM user WHERE age = 18 FOR UPDATE;
锁的位置:
-
• 锁的是 二级索引 idx_age 中 age = 18 的所有索引项 -
• 然后根据这些索引项找到主键,再锁主键索引对应的记录
这是“二级索引加锁 → 回表 → 主键索引加锁”的过程。
六、最危险的情况:未命中索引会锁全表
如果写:
SELECT * FROM user WHERE name = 'Tom' FOR UPDATE;
而 name 无索引,会发生什么?
InnoDB 必须对全表扫描
→ 全表扫描使用主键索引
→ 每扫描到一行都会尝试加锁
→ 最终导致“锁全表”
这就是为什么:
-
• 索引缺失会导致死锁频发 -
• 范围加锁范围巨大 -
• 锁等待时间变长
七、范围查询为什么会锁一大片?(Gap Lock 原理)
SQL:
SELECT * FROM user WHERE age BETWEEN 10 AND 20 FOR UPDATE;
假设索引树中 age 有:
8
12
16
25
加锁范围:
(8,12], (12,16], (16,20]
也就是:
-
• 记录锁:12、16 -
• 间隙锁:索引项之间的 gap -
• 临键锁:Record + Gap(InnoDB 默认锁法)
👉 这些都是索引上的锁,而不是数据行上的锁。
八、DELETE 和 UPDATE 为什么会锁更多?
例如:
UPDATE user SET age = age+1 WHERE age = 18;
流程:
-
1. 走二级索引 idx_age → 锁住 age=18 的所有索引项(Record Lock) -
2. 回表锁住主键记录 -
3. 因为会更改索引值,会对索引对应 gap 加锁
所以 DELETE / UPDATE 的加锁范围通常更大。
九、索引锁的本质图示
假设索引树如下:
8 --- 12 --- 16 --- 25 (B+Tree 索引项)
执行:
SELECT * FROM user WHERE age=16 FOR UPDATE;
加锁:
Record Lock → [16]
Gap Lock → (12,16), (16,25)
Next-Key → (12,16], (16,25]
👉 锁的都是 “索引节点”,不是物理行。
十、如何利用这个原理优化死锁与锁等待?
理解“锁在索引上”后,很多问题都能解释:
1)加合适的索引 → 减小锁范围
没有索引:
-
• 扫描全表 -
• 锁全表 -
• 死锁几率暴增
2)精准查询优于范围查询
使用:
WHERE id = 10
比:
WHERE age BETWEEN 10 AND 20
更安全,因为不用锁 gap。
3)提前设计访问顺序
让事务按统一顺序访问索引项 → 避免死锁。
4)避免在高并发情况下使用无索引更新
例如:
UPDATE user SET status = 1 WHERE name='Tom';
name 没索引时是灾难。
十一、总结
InnoDB 的锁加在索引项上,而不是加在物理行上。
因为 InnoDB 是索引组织表,行数据存储在主键索引中,二级索引保存主键值。
所有行锁本质都是索引锁,包括记录锁、间隙锁和临键锁。
如果没有可用索引,会对全表主键索引加锁,因此索引设计不当会导致锁范围大、死锁概率高。

