大数跨境
0
0

InnoDB 加锁到底发生在索引还是行?

InnoDB 加锁到底发生在索引还是行? Linux运维技术之路
2025-12-03
3
导读:InnoDB 加锁到底发生在索引还是行?数据库面试中,有一个高频却容易答错的问题:InnoDB 的加锁到底是锁“行”还是锁“索引”?

 










 

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. 1. 走二级索引 idx_age → 锁住 age=18 的所有索引项(Record Lock)
  2. 2. 回表锁住主键记录
  3. 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 是索引组织表,行数据存储在主键索引中,二级索引保存主键值。
所有行锁本质都是索引锁,包括记录锁、间隙锁和临键锁。
如果没有可用索引,会对全表主键索引加锁,因此索引设计不当会导致锁范围大、死锁概率高。

 



 

 


往期回顾

【声明】内容源于网络
0
0
Linux运维技术之路
专注运维架构、高可用、高并发、高性能、大数据、容器化、数据库、python、devops等开源技术和实践的分享。
内容 347
粉丝 0
Linux运维技术之路 专注运维架构、高可用、高并发、高性能、大数据、容器化、数据库、python、devops等开源技术和实践的分享。
总阅读774
粉丝0
内容347