核心观点:索引快不快,不看有没有走索引,而看扫了多少行、回表多少次、数据分布如何。
一、缘起:最左前缀原则
一切从一个看似简单的问题开始:
索引的最左前缀原则,只适用于联合索引吗?
答案是肯定的。
最左前缀是 联合索引(Composite Index) 的专属概念,和 SQL 中 WHERE条件的书写顺序无关,只和索引定义时的字段顺序有关。
css
CREATE INDEX idx_a_b_c ON t(a, b, c);
能走索引的查询:
ini
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3
不能走索引的查询:
ini
WHERE b = 2
WHERE b = 2 AND c = 3
更重要的是,我纠正了自己一个误区:
SQL 里条件的左右顺序不重要,索引定义里的左右顺序才重要。
MySQL 优化器会自动重排条件,WHERE b=1 AND a=1和 WHERE a=1 AND b=1对优化器来说没有任何区别。
二、EXPLAIN 的 type:判断索引好坏的第一指标
理解了最左前缀后,自然引出了另一个问题:
我怎么知道索引到底用得好不好?
答案是看 EXPLAIN的 type字段。
性能从好到坏:
sql
const > eq_ref > ref > range > index > ALL
const/eq_ref:主键 / 唯一索引,极致性能ref:普通索引等值查询,理想状态range:索引范围扫描,可接受index:全索引扫描,警惕ALL:全表扫描,必须优化
慢查询优化的底线:至少达到 range。
三、千万级数据下的索引实验
为了验证这些理论,我脑补了一个千万级订单表的场景,并逐步推演。
1️⃣ 联合索引 + 范围查询
假设有 1000 万订单,1000 个业务员,索引如下:
scss
CREATE INDEX idx_name_price ON orders(name, order_price);
查询:
sql
SELECT * FROM orders
WHERE name IN ('a', 'b')
AND order_price > 100;
结论:
- ✅ 会走
idx_name_price - ✅
type = range - ❌ 但
SELECT *会导致大量回表
2️⃣ 回表:慢查询的隐形杀手
回表指的是:
通过二级索引拿到主键 ID,再回到聚簇索引取完整数据。
回表本身不慢,慢的是"大量随机回表" 。
于是我意识到一个经典优化手段:延迟回表
sql
-- 第一步:只拿 ID
SELECT id FROM orders
WHERE name IN ('a','b') AND order_price > 100;
-- 第二步:用 ID 查详情
SELECT * FROM orders WHERE id IN (...);
✅ 第一次查询:纯索引扫描,无回表
✅ 第二次查询:主键精准定位,顺序 IO
回表慢,不是因为"回",而是因为"乱回"。
3️⃣ 范围查询 vs 等值查询
继续推演,如果把条件换成:
sql
SELECT id, price FROM orders WHERE price > 100;
即使 price上有索引,且是覆盖索引(Using index),也可能并不快。
原因是:
price > 100可能命中 30%~50% 的数据- 扫描行数太多
- 优化器甚至可能放弃索引,选择全表扫描
而如果换成等值查询:
ini
SELECT id, price FROM orders WHERE price = 100;
情况完全不同:
type = ref- 扫描行数极少
- 即使数据完全无序,B+Tree 也能毫秒级定位
等值查询是索引的"王炸",范围查询才是需要警惕的。
四、字符串索引:地址查询的现实拷问
最后,我把问题推向了更复杂的现实场景:
如果给"地址"这种长文本建索引,在千万级数据下,等值查询还能瞬间返回吗?
答案是:能,但有代价。
✅ 为什么能快?
- B+Tree 不在乎数据类型
- 字符串等值查询同样是 O(log N)
- 查找复杂度与数字索引一致
❌ 为什么工程上很少这么做?
- 索引体积大:字符串比数字大 5~10 倍,Buffer Pool 压力大
- 写入成本高:页分裂概率高,写多场景性能下降
- 数据不稳定:地址输入不规范,等值查询命中率低
- 查询需求不匹配 :真实场景多为模糊查询(
LIKE '%福田%')
✅ 更合理的做法
- 拆字段:
province,city,district - 地址标准化 + 哈希索引
- 复杂搜索交给 Elasticsearch
不建地址索引,不是因为慢,而是因为"不值"。
五、总结:我对索引认知的四个层次
通过这次复盘,我对索引的理解从"会用"进化到了"懂权衡":
- 语法层 :知道
CREATE INDEX,知道最左前缀 - 诊断层 :会看
EXPLAIN,理解type/rows/Extra - 性能层:明白回表、覆盖索引、等值 / 范围查询的差异
- 工程层:懂得索引的成本、数据分布的影响、业务场景的匹配
一句话概括:
**索引解决的是"过滤",而不是"扫描";
快不快,不看有没有走索引,而看扫了多少行。**
附:面试速记口诀
- 最左前缀看定义,不看 SQL 顺序
- type 至少到 range,避免 ALL 和 index
- 等值查询是王炸,范围查询看比例
- 回表怕多不怕有,主键回表最稳妥
- 字符串能快但不值,地址拆分更合理
📌 这篇文章已经是一个完整的技术复盘了 ,你发到博客上,就是一份非常扎实的 MySQL 索引学习笔记 + 面试素材。
如果你愿意,下一步我可以帮你:
- ✅ 给它起 3~5 个爆款标题(适合掘金 / 知乎)
- ✅ 缩写成一个 "面试 3 分钟自我介绍版"
- ✅ 再补一篇番外:《面试官问我:那索引是不是建得越多越好?》
你直接说下一步想干嘛 👍