
为什么 EXPLAIN 显示 type=ALL 却以为加了索引?
不是所有“加了索引”的字段都能被用上。MySQL 优化器会根据统计信息、数据分布、查询条件顺序和索引结构综合判断是否走索引。比如对 WHERE a = ? AND b > ? ORDER BY c 建了 (a, b, c) 复合索引,看起来完美,但如果 b > ? 匹配范围过大(比如占表 30% 以上),优化器可能直接放弃索引走全表扫描——它算出来走索引回表成本更高。
实操建议:
- 用
EXPLAIN FORMAT=JSON看filtered字段,低于 10 表示选择性差,索引大概率被跳过 - 避免在索引列上做函数操作:
WHERE YEAR(created_at) = 2024会让created_at索引失效,改用created_at BETWEEN '2024-01-01' AND '2024-12-31' - 复合索引最左前缀原则不是“只要开头匹配就行”,而是“连续匹配前缀”。
(a,b,c)支持a、a,b、a,b,c,但不支持a,c或单独c
JOIN 时驱动表选错导致索引没用上
MySQL 的 JOIN 是嵌套循环,先确定驱动表(外层),再用被驱动表(内层)的索引去匹配。如果优化器误判小表为被驱动表,就会让大表做全表扫描,哪怕大表上有索引也白搭。
常见错误现象:
- 明明
t1只有 100 行,t2有 100 万行,SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id却显示t2走ALL -
STRAIGHT_JOIN强制指定顺序后,EXPLAIN突然显示索引生效
实操建议:
- 给被驱动表的
ON字段建索引,比如t2.t1_id必须有索引,且类型要和t1.id完全一致(包括是否NOT NULL、字符集、长度) - 用
STRAIGHT_JOIN临时验证:把小表放前面,显式写成SELECT STRAIGHT_JOIN * FROM t1 JOIN t2 ON ... - 检查字段隐式转换:
t1.id是BIGINT,t2.t1_id是VARCHAR,会导致索引失效,EXPLAIN的Extra里会出现Using where; Using join buffer
ORDER BY 和 LIMIT 组合让索引“半失效”
即使 WHERE 条件命中了索引,如果 ORDER BY 字段不在该索引中,MySQL 就得先按索引取出所有满足条件的行,再额外排序——这时索引只用于过滤,不用于排序,EXPLAIN 会显示 Using filesort。
性能影响明显:10 万行匹配结果,ORDER BY created_at LIMIT 10 可能触发磁盘排序;而覆盖索引可直接按顺序返回前 10 条。
实操建议:
- 把
ORDER BY字段追加到WHERE所用索引末尾,例如原索引是(status, user_id),查询带ORDER BY created_at,就建(status, user_id, created_at) - 如果
SELECT *,注意索引不能覆盖所有字段,回表成本高;可考虑冗余常用字段进索引,或改用SELECT id+ 应用层二次查主键 -
LIMIT很小(如LIMIT 10)时,确保排序字段有高选择性,否则优化器仍可能放弃索引
多表 JOIN 后 GROUP BY 没走索引
复杂查询里,JOIN 结果集再 GROUP BY,很容易触发临时表 + 文件排序。即使每个单表都有索引,合并后的中间结果集也无法复用原有索引结构。
典型场景:统计每个用户的订单数+最近下单时间,写成 SELECT u.name, COUNT(o.id), MAX(o.created_at) FROM user u JOIN order o ON u.id = o.user_id GROUP BY u.id,EXPLAIN 显示 Using temporary; Using filesort。
实操建议:
- 优先考虑子查询下推:
SELECT u.name, t.cnt, t.max_time FROM user u JOIN (SELECT user_id, COUNT(*) cnt, MAX(created_at) max_time FROM order GROUP BY user_id) t ON u.id = t.user_id,让聚合在order表内部完成,再关联 -
GROUP BY字段必须是驱动表的主键或唯一索引字段,否则无法利用索引有序性;如果GROUP BY u.name(非唯一),基本无法避免临时表 - 确认
tmp_table_size和max_heap_table_size设置足够大,避免内存临时表退化为磁盘临时表
真正难的不是建索引,是看懂 EXPLAIN 里那几行输出背后的数据分布假设。很多“索引失效”其实不是 MySQL 不想用,而是它算出来——用比不用还慢。


















