SQL 优化(覆盖索引 / 回表 / 下推 / join 原理)
1️⃣ 覆盖索引(Covering Index)
核心理念:
“如果索引里有我需要的所有列,就不用回表读数据页。”
例子:
SELECT id, name FROM user WHERE age = 20;
假设你有索引 (age, id, name),那么:
- 查询可以直接在 B+Tree 叶子节点找到 id、name
- 不用访问聚簇索引(回表)
- I/O 极少,查询飞快
✅ 覆盖索引优势:减少回表 → 减少磁盘随机 I/O → 减少 Buffer Pool 命中压力
❌ 注意:
- 覆盖列过多,索引膨胀 → 写入慢
- 不包含查询列 → 回表不可避免
2️⃣ 回表(Index Lookup / Clustered Index Lookup)
核心理念:
二级索引叶子只存主键,查到结果必须回聚簇索引(主键索引)拿完整数据。
流程:
- 查二级索引
(age)→ 找到 id=100 - 再去聚簇索引找 id=100 → 拿整行数据
💥 性能杀手:
- 单行回表 OK
- 范围查询/大批量回表 → 大量随机 I/O → 慢
优化方式:
- 使用覆盖索引
- 调整索引列顺序,让二级索引包含查询列
- 减少 SELECT *
3️⃣ 下推(Predicate Pushdown / Index Condition Pushdown)
核心理念:
尽量让索引过滤掉更多行,而不是先拿到行再在内存过滤。
传统:
SELECT * FROM user WHERE age = 20 AND name LIKE 'Tom%';
- 先用索引 age 找到很多行
- 再回表读整行 → 再在内存中过滤 name
下推优化:
- InnoDB 5.6+ 支持 Index Condition Pushdown (ICP)
- 在索引叶子节点直接判断
name LIKE 'Tom%' - 减少回表次数
💡 画面感:
你像在 B+Tree 叶子节点做“二次过滤”,避免不必要的随机 I/O。
4️⃣ Join 原理(Nested Loop + Hash + Merge)
MySQL 常用 join 策略:
1️⃣ 嵌套循环(Nested Loop Join,默认 InnoDB 方式)
SELECT *
FROM A JOIN B ON A.id = B.a_id;
流程:
- 外表 A 一行一行读
- 用 B 的索引查匹配行(Index Lookup)
- 返回结果
✅ 优点:简单、索引优化效果好
❌ 缺点:大表 join 大表 → 随机 I/O 多 → 慢
2️⃣ Batched Key Access / Block Nested Loop
优化版 Nested Loop:
- 批量从 B 取索引 → 减少随机 I/O
- InnoDB 5.6+ 自带
3️⃣ Hash Join(MySQL 8.0+ 支持部分场景)
- 小表 build hash table
- 大表 probe hash → 快速匹配
- 减少嵌套循环次数
4️⃣ Merge Join(排序合并)
- 两个表先排序
- 顺序扫描匹配 → 减少随机 I/O
- MySQL InnoDB 默认不常用,需要 optimizer hint
5️⃣ 工程师视角总结优化思路
| 优化手段 | 作用 | 场景 |
|---|---|---|
| 覆盖索引 | 减少回表 | SELECT 列少,二级索引列可覆盖 |
| 回表优化 | 尽量少回表 | 避免 SELECT * / 调整索引列 |
| 下推(ICP) | 索引页过滤 | 范围查询 + 二次条件 |
| Join 顺序优化 | 外表小 → 内表大 | 避免大表嵌套循环 |
| 合理索引 | 包含 join/on/filter 字段 | 减少随机 I/O |
6️⃣ 工程实践技巧(你实际能用的)
-
EXPLAIN + Buffer Pool
- 看 join 顺序、索引使用、回表次数
- 判断覆盖索引命中情况
-
**少用 SELECT ***
- 回表成本高,覆盖索引才真正快
-
索引列顺序
- WHERE 列前置 → 提高索引命中
- join/on 字段在索引里 → 减少全表扫描
-
小表驱动大表
- Nested Loop join 顺序调整 → 减少随机 I/O
浙公网安备 33010602011771号