跳转至

02. MySQL 事务、锁、索引进阶(深挖版)

这一章经常是 MySQL 面试里真正拉开差距的地方。因为到这里,面试官通常已经不满足于你会背:

  • 最左前缀
  • 行锁、表锁、间隙锁
  • 死锁会回滚一个事务
  • explain 看 type、key、rows

他们更想知道你是否真的理解:

  • 索引为什么会失效
  • 锁到底锁的是"行"还是"索引范围"
  • 死锁为什么不可完全避免
  • explain 到底怎么看出 SQL 是否靠谱

本章建议按“先理解知识主线,再练问答表达,最后吃透边界条件”的顺序阅读:

  • 基础必会 - 聚簇索引、索引失效、最左前缀
  • 高频追问 - 行锁/表锁/间隙锁、死锁、explain 分析
  • 深度难点 - 为什么建了索引也可能不走、综合调优

先把这一章的知识骨架搭起来

这一章要解决的核心问题是:数据库怎样在并发写入和高频查询同时存在时,尽量兼顾正确性和性能。事务、锁、MVCC、索引、执行计划都在围绕这个目标服务。

你可以把内容拆成两条线:一条是“写入正确性线”,也就是 ACID、隔离级别、行锁/间隙锁/死锁、提交回滚;另一条是“查询效率线”,也就是 B+ 树索引、最左前缀、回表、覆盖索引、联合索引、执行计划。真正的面试难点在于这两条线经常会交叉:比如一个看似普通的索引设计,会直接影响加锁范围和并发性能。

所以这章必须把“正确性”和“效率”放在一起理解。


第一部分:先把概念和主线讲清楚

进入问答前,先把最小前置知识补齐

这一章其实在回答一个很朴素但很难的问题:数据库怎样在“很多人同时读写”时,尽量兼顾正确性和效率。事务、锁、MVCC 负责正确性侧,索引、执行计划、回表、覆盖索引负责效率侧。

更麻烦的是这两侧经常互相影响:索引不合理会扩大扫描和加锁范围,隔离级别变化会影响并发性能,死锁和回滚又会反过来拖慢业务。先把“正确性线”和“性能线”两条主线建立起来,这章才不会变成杂乱问答。


1. 什么是聚簇索引?

标准回答

InnoDB 的主键索引通常是聚簇索引,叶子节点直接保存整行数据,因此表数据本身按主键组织。

更深入的理解

很多人把聚簇索引背成定义,但真正关键的是它的工程含义:

  • 主键不只是"一个逻辑唯一标识"
  • 它还影响整张表数据的物理组织方式

这会带来两个直接后果:

  1. 主键查询通常路径最短
  2. 主键设计会影响插入局部性和二级索引体积

面试高分点

在 InnoDB 里,主键不仅是约束,也是数据组织方式本身的一部分。


2. 什么情况下会索引失效?

常见情况

  • 没遵循最左前缀
  • 对索引列做函数/表达式操作
  • 隐式类型转换
  • like 以 % 开头
  • 选择性过差导致优化器放弃索引

为什么叫"失效"?

不是索引真的坏了,而是:

  • 优化器判断用索引不划算
  • 或者查询条件没法利用索引有序性

典型场景拆解

2.1 没遵循最左前缀

联合索引是按从左到右的键序组织的,如果跳过左边列,后面的有序性往往就利用不起来。

2.2 对索引列做函数/表达式

比如:

  • where year(create_time) = 2025

索引里存的是原始列值,不是函数计算后的结果,这会破坏可直接查找路径。

2.3 隐式类型转换

比如字符串列拿数字比较,可能导致数据库不得不做转换,影响索引使用。

2.4 前导通配符

like '%abc' 通常无法利用普通 B+ 树索引前缀有序性。

2.5 优化器主动放弃

如果扫描比例太大,优化器可能觉得:

  • 走索引 + 回表
  • 反而不如直接全表扫

高分点

"索引失效"很多时候不是规则背诵问题,而是因为查询条件破坏了索引可利用的有序访问路径,或者优化器判断成本不划算。


3. 最左前缀原则是什么?

标准回答

联合索引按最左字段开始匹配,只有从左往右连续使用索引列时,才能充分利用索引能力。

更深入的理解

联合索引 (a, b, c) 可以理解为按:

  • 先比 a
  • 再在相同 a 下比 b
  • 再在相同 a,b 下比 c

所以:

  • a 可以
  • a,b 可以
  • a,b,c 可以
  • 直接只用 bc 往往不行

面试别答得太死

不是所有情况都只能机械地说"后面完全用不了",因为还可能涉及:

  • 索引下推
  • 覆盖索引
  • 优化器不同执行计划

但基础原则一定要稳。


第二部分:围绕高频追问继续展开

4. 什么是行锁、表锁、间隙锁?

行锁

锁某些记录。

表锁

锁整张表。

间隙锁

锁索引记录之间的区间,常用于防止幻读。

更准确地说

InnoDB 里大家口头说"行锁",但它很多时候依赖的是:

  • 索引项定位
  • 锁定某个索引记录或范围

所以如果没有合适索引,所谓"行锁"效果可能很糟,甚至扩大成更大范围影响。

为什么间隙锁难理解?

因为它锁的不是"已有那一行",而是:

  • 某个索引区间
  • 防止别人往这个范围插新记录

这正是它防幻读的关键。


5. 什么是死锁?数据库里怎么处理?

标准回答

事务相互等待对方持有的锁,形成循环等待。

MySQL 常见处理

数据库会检测死锁并回滚其中一个事务。

为什么死锁不可完全避免?

只要系统里存在:

  • 并发事务
  • 多资源竞争
  • 获取顺序不一致

理论上就可能死锁。

所以真正工程里的目标不是"绝不死锁",而是:

  • 降低死锁概率
  • 出现时快速恢复
  • 让业务具备重试能力

常见降低方法

  • 固定访问资源顺序
  • 缩短事务时间
  • 尽量走同样索引路径
  • 控制批量更新规模
  • 出错后幂等重试

高分点

死锁不是数据库"太差",而是并发资源竞争的自然结果。数据库能做的是检测并牺牲一方,业务要做的是减少发生概率并正确重试。


6. explain 主要看什么?

常见关键字段

  • type
  • key
  • rows
  • extra

怎么理解这些字段?

6.1 type

看访问方式粗不粗。 通常越接近精准访问越好,越接近全表扫越危险。

6.2 key

看实际用了哪个索引,而不是"你以为会用哪个索引"。

6.3 rows

看优化器预估要扫多少行。 即便不是绝对准确,也能反映这个 SQL 大概重不重。

6.4 extra

常见高频关注点:

  • 是否使用覆盖索引
  • 是否有临时表
  • 是否有文件排序
  • 是否有额外过滤

面试高分点

explain 不是背字段定义,而是用来快速判断:有没有走对索引、扫描是否过大、排序/分组是否产生了额外代价。


第三部分:把难点、边界和代价吃透

7. 为什么"走了索引"也可能很慢?

这是非常高频的追问。

原因可能包括

  • 命中行太多
  • 回表很多
  • 过滤性差
  • 排序/分组没利用索引
  • 走错了索引
  • 索引虽然用了,但不是高效路径

所以不能只看"用了 key 没"

更完整要看:

  • 用的是不是合适索引
  • 扫了多少行
  • 有没有回表
  • 有没有 filesort / temporary

8. 一组典型追问链

  1. 聚簇索引为什么重要?
  2. 联合索引为什么强调最左前缀?
  3. 索引失效常见原因有哪些?
  4. 为什么加了索引 SQL 还是慢?
  5. InnoDB 的行锁真的是锁"行"吗?
  6. 间隙锁怎么防幻读?
  7. 死锁为什么不可完全避免?
  8. explain 应该怎么看?

9. 一份更像面试现场的总结回答

MySQL 进阶题的核心,不是孤立背几个术语,而是理解索引、锁和执行计划本质上都在回答同一个问题:数据库怎样在一致性、并发和性能之间做平衡。聚簇索引决定了数据组织方式,联合索引决定了访问路径,锁决定了并发边界,explain 则帮助我们观察优化器到底选择了什么代价路径。真正好的回答,既要能说定义,也要能说执行代价和工程后果。


10. 复习建议

至少做到:

  • 能解释最左前缀的根本原因
  • 能说清"索引失效"很多时候是访问路径不再划算
  • 能理解 InnoDB 的锁和索引范围关系密切
  • 能说明死锁为什么本质上无法完全消灭
  • 能用 explain 去判断 SQL 是否真的高效

做到这里,这一章就开始接近真实后端排障和 SQL 调优能力了。