从最左前缀到地址索引:我对 MySQL 索引的一次完整复盘

核心观点:索引快不快,不看有没有走索引,而看扫了多少行、回表多少次、数据分布如何。


一、缘起:最左前缀原则

一切从一个看似简单的问题开始:

索引的最左前缀原则,只适用于联合索引吗?

答案是肯定的。

最左前缀是 联合索引(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=1WHERE a=1 AND b=1对优化器来说没有任何区别。


二、EXPLAIN 的 type:判断索引好坏的第一指标

理解了最左前缀后,自然引出了另一个问题:

我怎么知道索引到底用得好不好?

答案是看 EXPLAINtype字段。

性能从好到坏:

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)
  • 查找复杂度与数字索引一致

❌ 为什么工程上很少这么做?

  1. 索引体积大:字符串比数字大 5~10 倍,Buffer Pool 压力大
  2. 写入成本高:页分裂概率高,写多场景性能下降
  3. 数据不稳定:地址输入不规范,等值查询命中率低
  4. 查询需求不匹配 :真实场景多为模糊查询(LIKE '%福田%'

✅ 更合理的做法

  • 拆字段:province, city, district
  • 地址标准化 + 哈希索引
  • 复杂搜索交给 Elasticsearch

不建地址索引,不是因为慢,而是因为"不值"。


五、总结:我对索引认知的四个层次

通过这次复盘,我对索引的理解从"会用"进化到了"懂权衡":

  1. 语法层 :知道 CREATE INDEX,知道最左前缀
  2. 诊断层 :会看 EXPLAIN,理解 type/ rows/ Extra
  3. 性能层:明白回表、覆盖索引、等值 / 范围查询的差异
  4. 工程层:懂得索引的成本、数据分布的影响、业务场景的匹配

一句话概括:

**索引解决的是"过滤",而不是"扫描";

快不快,不看有没有走索引,而看扫了多少行。**


附:面试速记口诀

  • 最左前缀看定义,不看 SQL 顺序
  • type 至少到 range,避免 ALL 和 index
  • 等值查询是王炸,范围查询看比例
  • 回表怕多不怕有,主键回表最稳妥
  • 字符串能快但不值,地址拆分更合理

📌 这篇文章已经是一个完整的技术复盘了 ,你发到博客上,就是一份非常扎实的 MySQL 索引学习笔记 + 面试素材

如果你愿意,下一步我可以帮你:

  • ✅ 给它起 3~5 个爆款标题(适合掘金 / 知乎)
  • ✅ 缩写成一个 "面试 3 分钟自我介绍版"
  • ✅ 再补一篇番外:《面试官问我:那索引是不是建得越多越好?》

你直接说下一步想干嘛 👍

相关推荐
郡杰1 小时前
Spring Boot 请求流转与 RESTful
后端
再吃一根胡萝卜2 小时前
Redis 都有哪些用处?都会用在什么地方?
后端
再吃一根胡萝卜2 小时前
分布式与分布式锁:从访问量到数据一致性
后端
再吃一根胡萝卜2 小时前
分库分表:为什么我不建议一上来就分表
后端
再吃一根胡萝卜2 小时前
MySQL 慢查询优化:从定位到根治的完整思路
后端
Nturmoils3 小时前
订单查询丢数据,查了半天原来是 WHERE 惹的祸
数据库·后端
叫我Paul就好3 小时前
自从用上 Agents View 之后, 我的心力交瘁感消失了
后端
SamDeepThinking3 小时前
组合还是继承?我在项目里主要看这两个判断
java·后端·面试
niuju3 小时前
一种基于 Agent 的需求规范化闭环实践:让合规与规范内建于流程
后端