为什么索引建了,查询还是慢?—— 索引与 SQL 优化
很多人的直觉是"慢查询?加个索引就好了"。但现实里常遇到:索引明明建了,查询还是走全表扫描。问题在于——索引能不能生效,取决于你的 SQL 写法有没有踩中它的规则。这一篇就讲清:索引长什么样、怎么才能用上它、以及怎么排查慢 SQL。
索引有两层:聚簇索引和二级索引
一张 InnoDB 表有两类索引。聚簇索引就是主键索引,它的叶子节点直接存整行数据,一张表只有一个。二级索引是你建的普通索引,它的叶子节点只存"索引列的值 + 主键值"。
这就引出一个关键概念——回表:通过二级索引查到主键,还要回到聚簇索引把整行数据捞出来,多一次 IO。比如你给 name 建了索引,SELECT * FROM t WHERE name='张三' 会先在 name 索引里找到主键 id,再回表取整行。回表是有成本的,能省就省。
怎么省?覆盖索引——让查询要的字段全在索引里。比如 SELECT name, age FROM t WHERE name='张三',如果你有 (name, age) 联合索引,name 和 age 都在索引叶子节点里,就不需要回表了,EXPLAIN 里会显示 Using index,这是最理想的情况。
联合索引和最左前缀
多个列组成的索引叫联合索引。它有个"最左前缀"规则,理解它得先理解 B+ 树的排序方式:联合索引 (a, b, c) 是先按 a 排,a 相同再按 b,再按 c。所以:
(a) ✓ 能走索引
(a, b) ✓ 能走
(a, b, c) ✓ 能走
(b, c) ✗ 走不了(跳过了最左的 a)
(b) ✗ 走不了本质是:B+ 树先按 a 有序,你跳过 a 直接查 b,索引没法帮你二分定位。同理,如果中间有范围查询,右边的列也失效了——a=1 AND b>5 AND c=3 里,a、b 能走索引,c 走不了,因为 b 已经是范围了,c 没法继续有序使用。所以设计联合索引的口诀是:等值列放前面,范围列放最后。
索引失效的常见坑
除了最左前缀,还有几个高频失效场景,都是"破坏了索引列的有序性":
- 对索引列用函数:
WHERE YEAR(create_time)=2024,列被函数一包,索引没法直接比较。 - 隐式类型转换:
phone是 varchar,写WHERE phone=13800138000(数字),字符串被转成数字比较,走全表。 - 左模糊查询:
LIKE '%张'走不了索引,LIKE '张%'能走——因为前缀匹配才能用索引定位起点。 - OR 连接非索引列、负向查询(
!=、NOT IN)通常也失效。
这些坑的共同点:让优化器没法利用索引的"有序"去定位。
慢 SQL 怎么排查:EXPLAIN
排查慢 SQL 的标准流程是:慢查询日志定位到具体语句 → EXPLAIN 看执行计划 → 对症优化。
EXPLAIN SELECT ... 会输出一张表,几个关键列:
- type:访问类型,从优到劣是
const(主键等值)>eq_ref>ref(普通索引等值)>range(范围)>index(全索引扫)>ALL(全表扫,最差)。 - key:实际用到的索引,
NULL表示没走索引。 - rows:预估扫描行数,越大越慢。
- Extra:
Using index(覆盖索引,最优)、Using filesort(额外排序,通常要优化)、Using temporary(用了临时表)。
看到 type=ALL 且 rows 很大,基本就是没走索引,按前面的规则找原因;看到 Using filesort,考虑把排序列加进索引。
几个常见优化手段:加合适索引(覆盖、联合最左匹配)、改写 SQL 避开失效场景、大表深分页(LIMIT 100000, 10)用"延迟关联"——先只查主键,再回表取数据。
串起来
索引不是"建了就一定快",它能不能生效取决于 SQL 是否踩中"最左前缀、不破坏有序性"这些规则。排查时用 EXPLAIN 看 type/key/Extra,type=ALL 和 Using filesort 是重点信号。这一套理解下来,面对慢查询就有了"从现象到根因"的路径,而不是盲目加索引。
下一篇讲慢查询优化实战:线上慢 SQL 到底怎么发现、怎么用 EXPLAIN 全字段定位根因、怎么对症优化并验证?