Skip to content

02 · 索引原理与优化:B+树 / 覆盖索引 / 最左匹配 ​

后端慢查询十有八九是索引没建对。本篇讲清楚索引为什么能让查询变快(B+树)、什么时候该建(覆盖索引)、复合索引的规则(最左匹配),以及索引的隐藏代价。

速览卡 ​

项内容
一句话定位索引的底层原理 + 建索引的判断框架,让慢查询变成"看得懂、改得动"
核心内容B+树为什么适合磁盘、覆盖索引、最左匹配、回表、索引失效的真相
读完能回答为什么这个 WHERE 不用索引?复合索引列的顺序怎么定?索引越多越好吗?
相关板块DB01 关系模型与 SQL | DB04 Schema 设计 | C10 数据建模与访问

一、索引的本质:用空间换"少读磁盘" ​

没有索引 = 全表扫描:一行行读,O(n)。 有索引 = 先查"目录"定位,再按指针取行,O(log n)。

磁盘是慢的:一次随机读约 0.1ms,CPU 一条指令约 1ns,差约 10 万倍。 索引的核心目标不是"少算",而是少读磁盘块。


二、为什么是 B+树,不是二叉搜索树 ​

结构为什么不适合磁盘
二叉搜索树树太高,一次查询要读很多层,每层一次磁盘 IO
B+树一个节点存多个键,树矮;叶节点串成链表,范围查询友好
text
B+树 特点:
- 非叶节点只存"键",不存数据 → 节点更紧凑,一层能装更多键
- 所有数据在叶节点,且按序串成链表 → 范围查询一次定位连读
- 树高通常 3~4 层 → 千万级数据 3~4 次磁盘 IO 定位到行

你不需要背 B+树的细节,但要知道:矮树 + 顺序链表 = 点查快、范围查快、稳定 O(log n)。


三、主键索引 vs 二级索引:回表 ​

索引类型叶子存什么
主键/聚簇索引整行数据(InnoDB 主键即聚簇)
二级索引索引键 + 主键值(不是整行)
text
二级索引查到主键 → 再用主键回聚簇索引取整行  =  回表

回表是"查到位置又去取数据"的额外 IO。能避免就避免——这就是覆盖索引的价值。


四、覆盖索引:查询所需列全在索引里 ​

sql
-- 索引 idx(a,b):只有 a、b 两列
SELECT a, b FROM t WHERE a = 1;   -- 覆盖:只读索引,不回表
SELECT a, b, c FROM t WHERE a = 1; -- 需要 c → 回表

判断框架:SELECT 的列和 WHERE 的条件列都落在同一个索引里,就避免了回表。高频查询可以考虑"把常用列做成覆盖索引"来省一次 IO。

代价:索引要存这些列 → 空间变大、写入变慢。只为真正高频的查询做覆盖索引。


五、复合索引与最左匹配 ​

sql
CREATE INDEX idx_user_status ON orders(user_id, status, created_at);

最左匹配:查询的等值条件必须从最左列开始连续使用,才能命中该索引前缀。

查询命中情况
WHERE user_id = 1✅ 用 user_id 前缀
WHERE user_id = 1 AND status = 'paid'✅ 用前两列
WHERE user_id = 1 AND status='paid' AND created_at > ...✅ 全用(范围列在最后)
WHERE status = 'paid'❌ 跳过了最左列,用不上
WHERE user_id = 1 AND created_at > ...⚠️ 只用到 user_id,created_at 断在范围

复合索引列的顺序怎么定 ​

规则说明
先放等值条件列= 能继续向前匹配
再放范围条件列>/</BETWEEN 之后的列用不上
区分度高的放前面但要注意"区分度"不是唯一标准,结合查询模式
一个索引服务"最常见查询"不要为每个字段单独建索引

六、索引失效:别信玄学,看执行计划 ​

以下"常见说法"很多是看版本和优化器的,正确姿势是 EXPLAIN:

场景实际情况
WHERE name LIKE '%xx'前导通配符通常用不上索引
WHERE YEAR(created_at) = 2024对列做函数/运算通常用不上(要改成范围)
隐式类型转换varchar 列配数字可能失效或全扫
结果集很大(超过一定比例)优化器认为全扫更划算,即使有索引也不用

判断框架:永远用 EXPLAIN 看 type(const/ref/range/index/ALL)。 ALL = 全表扫描,是需要警惕的信号;但"结果集大时全扫"是合理决策,不是 bug。


七、索引的代价与取舍 ​

收益代价
读更快写入变慢(每插一行要维护所有索引)
覆盖查询占磁盘/内存
约束(唯一索引)建错索引拖慢高频写

判断框架:

  • 读多写少 → 大胆建索引
  • 写多读少 → 只建必要索引
  • 大表加索引要评估在线 DDL(ALGORITHM=INPLACE)
  • 不是索引越多越好,一个复合索引常能顶多个单列索引

八、自检清单 ​

  • [ ] 能说出 B+树适合磁盘的两个原因(矮树 + 顺序链表)
  • [ ] 知道主键索引叶子存整行、二级索引叶子存主键
  • [ ] 知道回表是什么,以及覆盖索引如何避免
  • [ ] 知道最左匹配,能把等值列放前面、范围列放后面
  • [ ] 慢查询会用 EXPLAIN 看 type,而不是猜
  • [ ] 知道索引有写入与空间代价,不为每个字段都建索引

九、常见误区 ​

误区事实
"索引越多越快"每个索引都拖慢写入、占空间
"建了索引就一定会用"优化器会评估,结果集大时可能全扫
"复合索引顺序无所谓"顺序决定能命中哪些查询(最左匹配)
"用函数包一下没关系"对列做运算通常让索引失效
"二级索引能直接取整行"二级索引叶子是主键,要回表
"加索引零成本"高频写表要谨慎,必要时在线 DDL

十、动手练习 ​

练习 A:设计复合索引 ​

表 orders(user_id, status, created_at, amount),最常用查询是"某用户的某状态订单按时间倒序取最近 20 条"。你会怎么设计索引?为什么?

练习 B:EXPLAIN 体检 ​

造 10 万行数据,分别对 WHERE user_id=1、WHERE status='x'、WHERE user_id=1 AND status='x' 跑 EXPLAIN,对比 type 与 rows。

练习 C:找出"自以为走了索引"的查询 ​

给一个 WHERE DATE(created_at) = '2024-01-01' 和一个 WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02',EXPLAIN 对比,说出为什么后者能用索引。


相关文档 ​

本区 ​

其他域 ​