找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

481

积分

1

好友

55

主题
发表于 3 天前 | 查看: 7| 回复: 0

数据库面试中,有一个高频却容易答错的问题:

InnoDB 的加锁到底是锁“行”还是锁“索引”?

很多同学会回答:锁的是行。 但实际上——

InnoDB 的锁是加在索引上的,不是行上的!如果表没有索引,加锁范围会变得不可控甚至锁全表。

为什么? InnoDB 到底锁了什么? 为什么范围查询会锁一大片? 为什么没索引会死锁频发?

一、核心结论:InnoDB 的锁加在索引上

你必须先记住一句话:

InnoDB 的所有行锁,本质上都是“索引项锁”。 如果没有可用索引,InnoDB 会进行全表扫描,并对所有扫描到的数据加锁。

也就是说:

  • 查询命中了索引 → 锁的是索引项
  • 查询没命中索引 → 会锁住全表对应所有索引项(相当于锁全表)

所以 InnoDB 并不存在“按行锁数据页”这种操作,而是通过“索引结构”来定位数据行并加锁。这也是为什么 数据库/中间件 优化中,索引设计至关重要。

二、底层原理:为什么锁是加在索引上的?

核心原因:InnoDB 是索引组织表 (Index Organized Table, IOT)

InnoDB 的数据本身就是按照主键索引(B+Tree)存储的。

这意味着:

  • 主键索引的叶子节点保存了完整的数据行。
  • 其他二级索引的叶子节点保存的是对应记录的主键值。

因此,要锁住一行数据,必须先访问索引。 因为无论是查询、更新还是删除记录,都必须通过索引来定位。所以,所有的锁操作最终都以“索引项”为单位进行。

三、各类行锁的本质:都是索引锁

InnoDB 的行锁(本质是索引锁)主要包括:

  • 记录锁 (Record Lock):锁定某一条具体的索引记录。
  • 间隙锁 (Gap Lock):锁定两个索引值之间的空隙,防止插入。
  • 临键锁 (Next-Key Lock):记录锁与间隙锁的组合,也是InnoDB默认的锁算法。
  • 插入意向锁 (Insert Intention Lock):一种特殊的间隙锁,表示插入意图。

无论哪种锁,其作用对象都是索引项或索引间的间隙

四、实例解析:不同索引下的加锁行为

表结构:

CREATE TABLE user (
  id INT PRIMARY KEY,
  age INT,
  name VARCHAR(20),
  KEY idx_age(age)
) ENGINE=InnoDB;

场景1:使用主键索引查询

SELECT * FROM user WHERE id = 10 FOR UPDATE;

锁的位置: 锁的是主键索引 B+Tree 上 id = 10 这一条索引项。它不会去锁name字段所在的“行”,加锁过程也无需访问二级索引。

场景2:使用二级索引查询

SELECT * FROM user WHERE age = 18 FOR UPDATE;

锁的位置:

  1. 首先,锁住二级索引 idx_age 中所有 age = 18 的索引项。
  2. 然后,根据这些索引项存储的主键值“回表”,到主键索引上锁住对应的记录。 这是一个“二级索引加锁 → 回表 → 主键索引加锁”的过程。

场景3:最危险的未命中索引查询

SELECT * FROM user WHERE name = 'Tom' FOR UPDATE;

如果 name 字段没有索引,会发生什么? InnoDB 只能进行全表扫描。而全表扫描是沿着主键索引进行的,每扫描到一行,都会尝试对其主键索引项加锁,最终导致锁住全表主键索引。这正是索引缺失导致死锁频发、性能骤降的根本原因。

五、范围查询与Gap Lock原理

SQL:

SELECT * FROM user WHERE age BETWEEN 10 AND 20 FOR UPDATE;

假设索引 idx_age 中现有值:8, 12, 16, 25

其加锁范围(Next-Key Lock)将是:

(8, 12], (12, 16], (16, 20]

解析:

  • 记录锁:锁住索引值为12和16的记录。
  • 间隙锁:锁住(8,12)、(12,16)、(16,20)这些索引区间。
  • 临键锁:上述锁的组合,即锁住(8,12](12,16](16,20]这几个“左开右闭”的区间。

这清晰地展示了锁是加在索引项及其间隙上的。当 MySQL 执行范围更新时,锁的范围可能远超你的预期。

六、DELETE/UPDATE为何锁更多?

以更新为例:

UPDATE user SET age = age + 1 WHERE age = 18;

其加锁流程比单纯SELECT更复杂:

  1. 在二级索引 idx_age 上,找到 age=18 的记录并加记录锁。
  2. 回表,对主键索引上的对应记录加锁。
  3. 因为修改了索引列的值,需要在旧的索引值前后加间隙锁,以防止其他事务插入新的age=18的记录。

因此,写操作的加锁范围通常比读操作更大。

七、从“索引锁”原理出发的性能优化实践

理解锁在索引上这一本质后,可以指导我们进行有效优化:

  1. 务必为查询条件添加合适索引 这是根本。没有索引会导致全表扫描加锁,锁范围巨大,并发度极低,死锁概率飙升。

  2. 精确查询优于范围查询 在业务允许的情况下,使用 WHERE id = ?WHERE age BETWEEN ? AND ? 更安全,因为它可能只需要一把记录锁,而无需间隙锁。

  3. 规范事务内的数据访问顺序 在高并发场景下,让所有事务都按相同的顺序(例如,按主键ID升序)访问记录,可以大幅降低死锁概率。

  4. 避免在高并发下执行无索引的DML操作UPDATE user SET status = 1 WHERE name='Tom'; 这样的语句,在name无索引时是性能灾难,应坚决避免。

八、总结

InnoDB的行锁实质是索引锁,而非物理数据行锁。这源于其索引组织表的存储结构:数据存于主键索引,二级索引引用主键。所有行锁操作,包括记录锁、间隙锁、临键锁,最终都作用在索引项上。若SQL无法命中索引,引擎将退而对全表主键索引加锁,从而引发严重的锁竞争与性能问题。深刻理解 “索引锁” 这一核心概念,是进行高效 数据库优化 和并发程序设计的关键前提。




上一篇:MySQL分区键实战解析:使用默认时间戳时能否为NULL?
下一篇:Spring Service注入优化:基于Lambda的统一调用组件实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-7 03:45 , Processed in 0.084233 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表