MySQL 什么时候会回表?
会,只要
SELECT的字段不在当前使用的索引中全部覆盖,且引擎是 InnoDB,就大概率发生回表。回表不是语法行为,而是 InnoDB 在二级索引(非聚簇索引)查到主键后,再拿着主键去聚簇索引(即主键索引)里捞完整行数据的过程。
典型触发场景:
WHERE条件走了二级索引,但
SELECT里包含了该索引没包含的列(比如
SELECT name, email FROM users WHERE city = 'Beijing',而
city是单独的二级索引)。 只有
SELECT *或含非索引列时才可能回表;如果
SELECT的全是索引列(包括联合索引中的部分或全部),就走“索引覆盖”,不回表
ORDER BY或
GROUP BY涉及非索引列,也可能迫使回表(即使
WHERE匹配了索引) 使用
LIKE 'xxx%'走了索引,但选了非索引列 → 同样回表
如何判断一条 SQL 是否回表?
看
EXPLAIN输出里的
Extra字段: 出现
Using index:说明走了索引覆盖,没回表 出现
Using where; Using index:也属于索引覆盖(
WHERE在索引上完成,无需回表取数据) 只出现
Using where(没带
Using index):大概率回表了 出现
Using index condition:用了 ICP(索引下推),仍可能回表——ICP 只是把部分
WHERE过滤下推到存储引擎层,不代表不取整行
EXPLAIN SELECT id, name FROM users WHERE city = 'Shanghai';
若
city是普通索引,而
name不在该索引中,则
Extra里不会出现
Using index,实际执行时就会回表。
联合索引怎么影响回表?
联合索引的顺序和
SELECT列是否被“覆盖”,直接决定回表与否。例如建立索引
INDEX idx_city_name_age (city, name, age):
SELECT city, name FROM ... WHERE city = 'X'→
Using index,不回表
SELECT city, name, age FROM ... WHERE city = 'X'→ 仍不回表(全在索引里)
SELECT city, name, email FROM ... WHERE city = 'X'→ 回表(
SELECT name FROM ... WHERE age = 25→ 无法用该联合索引(最左前缀不匹配),可能走全表或其它索引,与回表无关
注意:
ORDER BY city, name可利用该索引避免文件排序;但
ORDER BY name单独出现,无法利用,可能触发回表+临时表+filesort。
回表对性能的影响有多大?
一次回表 ≈ 一次随机主键查找(B+ 树搜索),在 SSD 上约 0.1–0.3ms,在 HDD 上可能达几毫秒。当扫描 10 万行二级索引记录,就要额外做 10 万次主键查找 —— IO 放大严重,极易成为瓶颈。
回表不可缓存:每次都要访问聚簇索引页,Buffer Pool 命中率低 并发高时,大量回表加剧 Buffer Pool 和磁盘争用 优化方向优先是“让查询走覆盖索引”,而不是盲目加索引;加宽联合索引前需评估写入开销和维护成本innodb_read_ahead_threshold等参数对回表无实质缓解作用,它只影响预读,不改变回表逻辑
真正容易被忽略的是:开发常以为「有索引=快」,却没检查
EXPLAIN的
Extra列,也没验证
SELECT列是否被索引完全覆盖——回表往往静默发生,直到慢查询日志爆发才被发现。
