02. MySQL 事务、锁、索引进阶(深挖版)¶
这一章经常是 MySQL 面试里真正拉开差距的地方。因为到这里,面试官通常已经不满足于你会背:
- 最左前缀
- 行锁、表锁、间隙锁
- 死锁会回滚一个事务
- explain 看 type、key、rows
他们更想知道你是否真的理解:
- 索引为什么会失效
- 锁到底锁的是"行"还是"索引范围"
- 死锁为什么不可完全避免
- explain 到底怎么看出 SQL 是否靠谱
本章建议按“先理解知识主线,再练问答表达,最后吃透边界条件”的顺序阅读:
- 基础必会 - 聚簇索引、索引失效、最左前缀
- 高频追问 - 行锁/表锁/间隙锁、死锁、explain 分析
- 深度难点 - 为什么建了索引也可能不走、综合调优
先把这一章的知识骨架搭起来¶
这一章要解决的核心问题是:数据库怎样在并发写入和高频查询同时存在时,尽量兼顾正确性和性能。事务、锁、MVCC、索引、执行计划都在围绕这个目标服务。
你可以把内容拆成两条线:一条是“写入正确性线”,也就是 ACID、隔离级别、行锁/间隙锁/死锁、提交回滚;另一条是“查询效率线”,也就是 B+ 树索引、最左前缀、回表、覆盖索引、联合索引、执行计划。真正的面试难点在于这两条线经常会交叉:比如一个看似普通的索引设计,会直接影响加锁范围和并发性能。
所以这章必须把“正确性”和“效率”放在一起理解。
第一部分:先把概念和主线讲清楚¶
进入问答前,先把最小前置知识补齐¶
这一章其实在回答一个很朴素但很难的问题:数据库怎样在“很多人同时读写”时,尽量兼顾正确性和效率。事务、锁、MVCC 负责正确性侧,索引、执行计划、回表、覆盖索引负责效率侧。
更麻烦的是这两侧经常互相影响:索引不合理会扩大扫描和加锁范围,隔离级别变化会影响并发性能,死锁和回滚又会反过来拖慢业务。先把“正确性线”和“性能线”两条主线建立起来,这章才不会变成杂乱问答。
1. 什么是聚簇索引?¶
标准回答¶
InnoDB 的主键索引通常是聚簇索引,叶子节点直接保存整行数据,因此表数据本身按主键组织。
更深入的理解¶
很多人把聚簇索引背成定义,但真正关键的是它的工程含义:
- 主键不只是"一个逻辑唯一标识"
- 它还影响整张表数据的物理组织方式
这会带来两个直接后果:
- 主键查询通常路径最短
- 主键设计会影响插入局部性和二级索引体积
面试高分点¶
在 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可以 - 直接只用
b、c往往不行
面试别答得太死¶
不是所有情况都只能机械地说"后面完全用不了",因为还可能涉及:
- 索引下推
- 覆盖索引
- 优化器不同执行计划
但基础原则一定要稳。
第二部分:围绕高频追问继续展开¶
4. 什么是行锁、表锁、间隙锁?¶
行锁¶
锁某些记录。
表锁¶
锁整张表。
间隙锁¶
锁索引记录之间的区间,常用于防止幻读。
更准确地说¶
InnoDB 里大家口头说"行锁",但它很多时候依赖的是:
- 索引项定位
- 锁定某个索引记录或范围
所以如果没有合适索引,所谓"行锁"效果可能很糟,甚至扩大成更大范围影响。
为什么间隙锁难理解?¶
因为它锁的不是"已有那一行",而是:
- 某个索引区间
- 防止别人往这个范围插新记录
这正是它防幻读的关键。
5. 什么是死锁?数据库里怎么处理?¶
标准回答¶
事务相互等待对方持有的锁,形成循环等待。
MySQL 常见处理¶
数据库会检测死锁并回滚其中一个事务。
为什么死锁不可完全避免?¶
只要系统里存在:
- 并发事务
- 多资源竞争
- 获取顺序不一致
理论上就可能死锁。
所以真正工程里的目标不是"绝不死锁",而是:
- 降低死锁概率
- 出现时快速恢复
- 让业务具备重试能力
常见降低方法¶
- 固定访问资源顺序
- 缩短事务时间
- 尽量走同样索引路径
- 控制批量更新规模
- 出错后幂等重试
高分点¶
死锁不是数据库"太差",而是并发资源竞争的自然结果。数据库能做的是检测并牺牲一方,业务要做的是减少发生概率并正确重试。
6. explain 主要看什么?¶
常见关键字段¶
typekeyrowsextra
怎么理解这些字段?¶
6.1 type¶
看访问方式粗不粗。 通常越接近精准访问越好,越接近全表扫越危险。
6.2 key¶
看实际用了哪个索引,而不是"你以为会用哪个索引"。
6.3 rows¶
看优化器预估要扫多少行。 即便不是绝对准确,也能反映这个 SQL 大概重不重。
6.4 extra¶
常见高频关注点:
- 是否使用覆盖索引
- 是否有临时表
- 是否有文件排序
- 是否有额外过滤
面试高分点¶
explain 不是背字段定义,而是用来快速判断:有没有走对索引、扫描是否过大、排序/分组是否产生了额外代价。
第三部分:把难点、边界和代价吃透¶
7. 为什么"走了索引"也可能很慢?¶
这是非常高频的追问。
原因可能包括¶
- 命中行太多
- 回表很多
- 过滤性差
- 排序/分组没利用索引
- 走错了索引
- 索引虽然用了,但不是高效路径
所以不能只看"用了 key 没"¶
更完整要看:
- 用的是不是合适索引
- 扫了多少行
- 有没有回表
- 有没有 filesort / temporary
8. 一组典型追问链¶
- 聚簇索引为什么重要?
- 联合索引为什么强调最左前缀?
- 索引失效常见原因有哪些?
- 为什么加了索引 SQL 还是慢?
- InnoDB 的行锁真的是锁"行"吗?
- 间隙锁怎么防幻读?
- 死锁为什么不可完全避免?
- explain 应该怎么看?
9. 一份更像面试现场的总结回答¶
MySQL 进阶题的核心,不是孤立背几个术语,而是理解索引、锁和执行计划本质上都在回答同一个问题:数据库怎样在一致性、并发和性能之间做平衡。聚簇索引决定了数据组织方式,联合索引决定了访问路径,锁决定了并发边界,explain 则帮助我们观察优化器到底选择了什么代价路径。真正好的回答,既要能说定义,也要能说执行代价和工程后果。
10. 复习建议¶
至少做到:
- 能解释最左前缀的根本原因
- 能说清"索引失效"很多时候是访问路径不再划算
- 能理解 InnoDB 的锁和索引范围关系密切
- 能说明死锁为什么本质上无法完全消灭
- 能用 explain 去判断 SQL 是否真的高效
做到这里,这一章就开始接近真实后端排障和 SQL 调优能力了。