什么是回表,为什么它会影响查询性能
回表是指 MySQL 在使用二级索引(非聚簇索引)查到主键值后,再根据主键回到聚簇索引(即主键索引的 B+ 树)中查找完整行数据的过程。每次回表都是一次随机 I/O,尤其在数据量大、内存不足时,性能损耗明显。
常见触发回表的场景包括:
SELECT *查询配合二级索引
SELECT中包含未被索引覆盖的字段
WHERE条件用了二级索引,但
ORDER BY或
GROUP BY需要额外字段
回表不是语法错误,而是执行计划里隐含的代价——
Extra列出现
Using index condition; Using where通常没问题,但若出现
Using where; Using index; Using filesort或没
Using index,就大概率在回表。
用覆盖索引避免回表:只查索引里有的字段
覆盖索引(Covering Index)是解决回表最直接的方式:让查询所需的所有字段都包含在同一个二级索引中,这样 MySQL 直接从索引叶节点取数,无需回主键树。
实操要点:
把WHERE条件字段放最前(符合最左前缀) 把
SELECT中所有字段都加进索引列尾部(顺序不关键,但要全包含)
ORDER BY字段若需避免排序,也建议加入索引(且顺序一致)
例如表
t_user有
(id, name, age, city),常查
SELECT name, age FROM t_user WHERE city = 'Beijing',建索引应为:
ALTER TABLE t_user ADD INDEX idx_city_name_age (city, name, age);而不是只建
INDEX(city)—— 后者会导致回表取
name和
age。
注意:索引列越多、越宽,写入开销和内存占用越大,别无脑堆字段。
联合索引顺序怎么排:WHERE、ORDER BY、SELECT 的优先级
联合索引字段顺序直接影响能否命中覆盖索引,也决定是否需要回表或排序。
判断顺序的依据是执行路径:
第一层:所有WHERE等值条件字段(按任意顺序,但必须连续出现在索引开头) 第二层:单个
ORDER BY字段(如果是范围查询如
city > 'A',后续字段无法用于排序或覆盖) 第三层:剩余
SELECT字段(补全覆盖需求)
错误示例:
SELECT name, age FROM t_user WHERE city = 'Beijing' ORDER BY age;如果建索引为
(city, age, name),能覆盖;但如果建为
(city, name, age),虽然也能查,但
ORDER BY age无法利用索引,会触发
Using filesort,还可能间接导致优化器放弃覆盖路径。
另一个坑:
IN和
OR条件会让等值判断失效,比如
WHERE city IN ('A','B') AND name = 'Tom',此时 city实际是范围扫描,
name后面的字段大概率无法用于覆盖。
用 EXPLAIN
验证是否真的避免了回表
光看 SQL 写得“好像”能覆盖不行,必须用
EXPLAIN FORMAT=TREE(MySQL 8.0+)或传统
EXPLAIN观察执行细节。
关键看三处:
type是
ref或
range(说明走了索引)
key显示实际使用的索引名
Extra中出现
Using index(表示覆盖索引生效),没有
Using where单独出现(否则说明还在回表后过滤)
特别注意:如果
Extra是
Using index condition,说明用了 ICP(Index Condition Pushdown),这是好现象,但不等于覆盖——它只是把部分
WHERE下推到存储引擎层过滤,仍可能回表取其他字段。
复杂点在于:即使你建了覆盖索引,MySQL 优化器也可能因统计信息不准、临时表、隐式类型转换等原因弃用它。所以每次改索引后,务必用真实数据量
EXPLAIN验证,别只在测试库跑几条
SELECT就认为 OK。
