加了索引仍慢的根本原因是未触发覆盖索引,必须通过explain确认extra中出现using index;回表发生于索引未包含select、where、order by等全部字段时,哪怕仅缺一个非索引列或使用函数/表达式即失效。

为什么加了索引还是慢?先看执行计划里有没有 Using index
回表不是加了索引就自动消失的。MySQL 只有在确认“这个索引里已经存了你要的所有字段”时,才会跳过回表。判断依据就是 EXPLAIN 输出的 Extra 列是否出现 Using index —— 这是覆盖索引生效的唯一铁证。
常见错误现象:type 是 ref 或 range,但 Extra 是空或只有 Using where,说明仍在回表。哪怕只查一个非索引字段(比如 SELECT name FROM user WHERE id = 123,而索引只建在 id 上),也会触发回表。
- 必须把
SELECT列、WHERE条件列、ORDER BY列、GROUP BY列全部包含进索引定义中 -
SELECT *几乎不可能被覆盖,除非你建的是主键索引(聚簇索引本身含全行) - 函数或表达式会破坏覆盖,比如
WHERE YEAR(create_time) = 2023,即使create_time在索引里,也无法覆盖
ALTER TABLE ... ADD INDEX 时怎么排字段顺序?
联合索引不是字段堆砌,顺序决定能否命中。核心原则是:高频过滤字段靠左,排序/分组字段居中,查询返回字段靠右。
比如订单表常用查询:SELECT order_id, status, amount FROM orders WHERE user_id = ? AND create_time BETWEEN ? AND ? ORDER BY create_time DESC。对应覆盖索引应为:ALTER TABLE orders ADD INDEX idx_covering (user_id, create_time, status, amount)。
-
user_id放最左:它是等值查询,能快速定位索引范围 -
create_time紧跟其后:范围查询(BETWEEN)必须放在等值之后,且排序字段需与查询方向一致(DESC要求索引也声明DESC,5.7+ 支持) -
status和amount放末尾:它们不参与过滤或排序,仅用于返回,放右边可压缩索引体积 - 别把
order_id加进去——它已是主键,InnoDB 二级索引叶子节点自带主键值,无需重复存储
哪些场景下覆盖索引反而会让性能更差?
覆盖索引不是万能膏药。它用空间换时间,但空间和写开销可能压垮系统。
- 索引总长度超 50 字节(尤其含
VARCHAR(255)或多个长文本字段):B+树层级变深,查询变慢,内存占用飙升 - 字段更新频繁(如
status每分钟改几次):每次 UPDATE 都要同步更新索引页,写放大严重 - 只读字段却建在高写入表上:比如给日志表的
message字段建覆盖索引,写入延迟明显升高 - 用
LIKE '%abc'查询时,即使字段在索引里,也无法利用覆盖(前导通配符导致索引失效)
实际取舍建议:优先覆盖高频、低更新、小体积的字段组合;对宽表,宁可拆成 2 个精简覆盖索引,也不建 1 个大而全的索引。
如何验证覆盖索引真的生效了?
别信猜测,要实测。除了 EXPLAIN,还得看真实 I/O 行为。
- 执行
EXPLAIN FORMAT=JSON SELECT ...,检查"using_index": true和"rows_examined_per_scan"是否显著下降 - 开启慢查询日志并设置
log_queries_not_using_indexes = OFF,避免误报干扰 - 对比优化前后
SHOW STATUS LIKE 'Handler_read%':覆盖索引生效时,Handler_read_key上升,Handler_read_rnd_next(回表标志)应趋近于 0 - 注意
SELECT COUNT(*):InnoDB 会直接用最小索引(通常是主键)统计,不走覆盖逻辑,别拿它当测试用例
最容易被忽略的是:覆盖索引只对单表查询有效。一旦涉及 JOIN,哪怕所有字段都在索引里,优化器也可能放弃覆盖——因为需要协调多表数据一致性,此时得靠 STRAIGHT_JOIN 或物化临时表来干预。











