MySQL 索引失效全解析:从 B+ 树结构看透每一条规则的本质

试被问到"索引失效的场景有哪些",你是不是也曾一口气背出"最左前缀、函数运算、左模糊、OR 条件......",然后看到面试官面无表情地点了点头?今天这篇博客,我们不背清单,从 B+ 树的结构出发,把每一条失效规则背后的"为什么"彻底搞明白。

一、先搞清楚:B+ 树凭什么能加速查询?

在聊失效之前,先回顾一下索引为什么快。

B+ 树索引之所以能加速查询,核心依赖两个特性:

  • 有序性:叶子节点按键值从小到大排列,支持二分查找快速定位
  • 前缀匹配:可以从一个确定的"起点"开始,沿着有序链表连续扫描

所有索引失效场景,本质上都是破坏了这两个特性中的某一个。 最终归结为三种破坏方式:

  • 找不到起点:无法确定从 B+ 树的哪个节点开始查找
  • 排序链断裂:后续列在全局无序,无法继续利用索引
  • 值域被改写:原始值有序 ≠ 计算后有序,索引排序失效

带着这三个关键词,我们逐个拆解。

二、最左前缀原则:排序链断裂

联合索引 (a, b, c) 的 B+ 树排序规则是:先按 a 排序,a 相同再按 b 排序,b 也相同再按 c 排序。

复制代码
WHERE a=1 AND b=2     -- 走索引
WHERE b=2 AND a=1     -- 走索引(优化器自动调整顺序)
WHERE b=2 AND c=1     -- 不走索引
WHERE c=1 AND a=2     -- 部分走索引

为什么 WHERE b=2 AND c=1 不走索引?

B+ 树的第一排序键是 a,跳过 a 直接查 bb 的值在全局是分散的(a=1 时有 b=10、20,a=2 时也有 b=10、20),MySQL 无法确定"b=2 从哪个节点开始找",只能全索引扫描。

为什么 WHERE b=2 AND a=1 能走索引?

MySQL 优化器会自动把条件重排为 WHERE a=1 AND b=2,WHERE 子句中条件的书写顺序不影响索引使用。

为什么 WHERE c=1 AND a=2 只走部分?

a 能用,但 c 因为中间缺少 b,排序链断裂,无法继续利用索引。

本质:索引的排序是从左到右逐层生效的,左边断了,右边就废了。

三、索引列运算/函数:值域被改写
复制代码
WHERE substring(phone, 10, 2) = '12'   -- ❌ 索引失效
WHERE YEAR(create_time) = 2023          -- ❌ 索引失效
WHERE id + 1 = 1001                     -- ❌ 索引失效

为什么索引会失效?

B+ 树存的是 phone原始值 ,按原始值的字典序排列。当你写 substring(phone, 10, 2) = '12' 时,MySQL 需要对每一行的 phone 值做截取运算,再判断结果是否等于 '12'。

问题在于:B+ 树是按原始 phone 值排序的,不是按截取后的结果排序的。原始值有序 ≠ 函数计算后有序。MySQL 无法从 B+ 树的根节点开始二分查找,只能逐行扫描。

修复方式:把运算移到等号右边。

复制代码
-- 改写前(索引失效)
WHERE id + 1 = 1001

-- 改写后(索引生效)
WHERE id = 1000

本质:对索引列做运算,等于改写了值域,B+ 树的排序规则不再适用。

四、模糊查询:起点丢失
复制代码
WHERE name LIKE '王%'    -- ✅ 索引生效
WHERE name LIKE '%三'    -- ❌ 索引失效
WHERE name LIKE '%王%三' -- ❌ 索引失效

为什么 LIKE '王%' 能用索引?

B+ 树的叶子节点按字典序排列,比如:张三 → 李四 → 王五 → 王六 → 赵七。前缀确定是"王",MySQL 可以直接在 B+ 树里定位到"王"开头的起点,然后顺着链表往后扫,遇到不以"王"开头的就停。起点确定,连续扫描,索引生效。

为什么 LIKE '%三' 索引失效?

前缀未知,"三"可能出现在任何名字的任意位置。MySQL 无法确定从哪个节点开始找,只能从头到尾遍历所有叶子节点。起点丢失,索引失效。

本质:左模糊导致前缀未知,B+ 树无法定位起始节点。

五、OR 条件:扫描成本反超
复制代码
WHERE name = '张三' OR age = 18   -- age 无索引,索引失效

为什么索引会失效?

name = '张三' 可以通过 B+ 树快速定位,age = 18 因为没有索引只能全表扫描。OR 的语义是"满足任一条件即可",MySQL 必须把两个结果集合并 后去重。既然 age = 18 已经需要全表扫描了,那再单独走一次 name 索引反而多此一举------优化器会直接选择全表扫描,顺便把两个条件都检查了。

修复方式 :给 age 也加上索引,或者用 UNION 改写。

本质:OR 中混入非索引列,导致全表扫描的成本反超索引扫描,优化器放弃索引。

六、覆盖索引:不是失效,而是优化

覆盖索引不是"失效"场景,而是优化手段。它的原理是:

非聚簇索引的叶子节点存的是主键值,查询时需要回表去聚簇索引拿完整数据。但如果你的查询只需要索引里已经包含的字段,MySQL 就不需要回表了。

复制代码
-- 假设建了联合索引 (user_id, amount)
SELECT user_id, amount FROM orders WHERE user_id = 200;

这条 SQL 只需要 user_idamount,它们都在非聚簇索引的叶子节点里,所以不需要回表,直接从索引里返回结果。EXPLAIN 的 Extra 列会显示 Using index

七、一张表总结
失效场景 破坏了 B+ 树的什么 本质
违反最左前缀 排序链断裂,后续列全局无序 找不到起点
索引列函数/运算 原始值有序 ≠ 计算后有序 值域被改写
LIKE 左模糊 前缀未知,无法定位起始节点 找不到起点
OR 含非索引列 需要全表扫描才能合并结果 扫描成本反超
隐式类型转换 等价于对索引列加了 CAST 函数 值域被改写
八、一句话总结

索引失效的本质就是破坏了 B+ 树的有序性------要么找不到起点,要么排序链断裂,要么值域被改写,导致 MySQL 无法利用索引的快速定位能力,只能退化为全表扫描。

理解了这一点,你就不需要死记硬背失效场景了。遇到任何 SQL,只要问自己一句:"这个条件还能不能利用 B+ 树的有序性快速定位?",答案自然就出来了。

相关推荐
delta_hell3 小时前
【阅读源码-Android】动画之AnimatorSet--2
android·源码·animatorset
Super 含4 小时前
Android 启动优化(五):线程、GC 与 IO 为什么会拖慢启动?
java·服务器·数据库
ltl5 小时前
向量检索引擎选型:决策树、RAG 回链与开放问题
数据库
坚持学习前端日记5 小时前
Python SQLAlchemy ORM 从0到1精通实战手册(基础到复杂高阶)
数据库·python·oracle
灯澜忆梦5 小时前
【MySQL17】进阶篇 | InnoDB引擎
数据库·mysql
anxiao_m5 小时前
2026制造业云桌面选型攻略,不同生产场景适配方案汇总
大数据·网络·数据库
许彰午6 小时前
# 数据库配拦截器,不用改BPMN
数据库
lv__pf6 小时前
redis【msb 2026金三银四redis上】
数据库·redis·缓存
叶辞树6 小时前
Android CLI
android
布莱克6057 小时前
理解数据库聚簇索引:原理、优势与适用场景
数据库·mysql