mysql覆盖索引如何减少回表_mysql性能优化技巧

来源:这里教程网 时间:2026-02-28 20:50:01 作者:

覆盖索引为什么能避免回表

MySQL 的二级索引(非聚簇索引)只存储索引列 + 主键值,不存整行数据。当

SELECT
的所有字段都包含在某个索引中时,优化器可以直接从该索引页拿到全部所需数据,无需再用主键去聚簇索引里查一遍——这个“再去主键索引找整行”的过程就叫回表。覆盖索引本质是让查询“止步于二级索引”,跳过回表开销。

如何判断一个查询是否走覆盖索引

EXPLAIN
Extra
列是否含
Using index
(注意不是
Using index condition
):

EXPLAIN SELECT user_id, status FROM orders WHERE status = 'paid';

如果

status
字段上有联合索引
(status, user_id)
,就会命中覆盖索引;但如果只查
user_id, status, created_at
,而
created_at
不在索引里,
Extra
就会变成
Using where; Using index
或干脆没有
Using index
,说明要回表。

Using index
✅ 表示纯覆盖索引扫描
Using index condition
❌ 表示用了 ICP(索引条件下推),但未必覆盖
Using where
+ 没有
Using index
❌ 基本确认要回表

设计覆盖索引的实操要点

覆盖索引不是越多越好,得按高频查询反向构建,且要注意顺序和冗余:

WHERE
条件字段放前面(满足最左前缀)
SELECT
中的其他字段追加在后面(尤其是
ORDER BY
GROUP BY
字段)
避免把大字段(如
TEXT
、长
VARCHAR
)放进索引——会显著增大索引体积,降低缓存效率
如果已有索引
(a, b)
,又新增查询常查
a, b, c
,优先考虑扩展为
(a, b, c)
,而不是新建
(a, b, c)
索引(否则
(a, b)
可能被废弃)

例如:常见分页查询

SELECT id, title, status FROM article WHERE status = ? ORDER BY create_time DESC LIMIT 20
,更适合建
(status, create_time, id, title)
而非
(status, create_time)
—— 后者仍需回表取
id
title

覆盖索引的隐性代价与陷阱

它省了回表,但可能带来别的负担:

索引变宽 → 更多磁盘 IO、更少页缓存命中率 →
SELECT *
场景下反而可能比窄索引+回表还慢
写入性能下降:每条
INSERT/UPDATE
都要维护更多索引字段
UPDATE
涉及覆盖索引中的任意字段时,该索引页必须更新(哪怕只改了没出现在索引里的列,只要该行被修改,且索引含其主键,就仍需更新索引中的主键副本)
JSON、Generated Column 等特殊类型字段若参与覆盖,需确认它们是否真正被索引存储(比如虚拟生成列必须显式
STORED
才能进索引)

真正难的不是建覆盖索引,而是权衡:哪些查询值得为它增加写开销和存储?哪些字段看似“顺手加进去”,实则让索引膨胀 3 倍?线上慢查分析时,先看

EXPLAIN
是否真触发了
Using index
,再看
Handler_read_index
Handler_read_next
的增长是否合理——别让“以为覆盖”变成“实际更慢”。

相关推荐