MySQL中的回表是什么?

回表是指在使用 非聚簇索引(二级索引)**进行查询时,若索引中未包含查询所需的全部数据,需要根据索引中的主键值回到聚簇索引(主键索引)**中查找完整数据行的过程。这一操作会增加额外的磁盘 I/O,可能对查询性能产生影响。

举一个例子。比如用户表,id是主键,name有索引。当执行SELECT * FROM users WHERE name='Arm'时,先通过name索引找到对应的id,再用id去主键索引里查整行数据。这时候就发生了两次索引查找,即回表。

select * from user where age = 20;需要根据二级索引(age)找到age = 20的主键键值,然后需要再根据主键去找整行的数据。主键索引频繁查询可能会造成大量随机I/O:

在磁盘上,数据是以数据页为单位进行存储和读取的。如果 age 为 20 的人很多,并且这些记录对应的主键 id 是无序且不连续的,那么在回表过程中,根据这些主键 id 到主键索引中查找记录时,就会需要频繁地访问不同的数据页。例如,一个 age 为 20 的记录对应的主键 id 是 10,下一个 age 为 20 的记录对应的主键 id 可能是 100,这两个 id 对应的记录很可能存储在不同的数据页上。磁盘在读取数据时,磁头需要移动到相应的位置才能读取数据。当需要频繁地访问不同的数据页时,磁头就需要不断地移动,这会带来大量的寻道时间和旋转延迟,也就是随机 I/O。与顺序 I/O 相比,随机 I/O 的性能要差很多,因为顺序 I/O 可以在一次磁头移动过程中连续读取多个数据页,而随机 I/O 每次都需要重新定位磁头,大大增加了磁盘 I/O 的开销。

举例:

假设 user 表中有如下部分数据:

id age
1 20
5 20
15 20
25 20

age 索引中可以快速找到 age 为 20 的记录对应的主键值 151525。但这些主键值在主键索引中对应的记录可能分散在不同的数据页上,例如 id 为 1 的记录在数据页 A,id 为 5 的记录在数据页 B,id 为 15 的记录在数据页 C,id 为 25 的记录在数据页 D。那么在回表时,就需要依次从数据页 A、B、C、D 中读取数据,这就导致了大量的随机 I/O。

所以当 age 为 20 的人很多且主键 id 无序不连续时,回表过程中主键索引的频繁查询会造成大量随机 I/O,从而影响查询性能。

如何避免回表:

可以使用覆盖索引

覆盖索引指索引包含查询所需的所有字段,无需回表即可返回结果:

sql 复制代码
 -- 查询只需 name 和 age,建立联合索引 (name, age)
 CREATE INDEX idx_name_age ON users(name, age);
 ​
 -- 执行以下查询时,无需回表
 SELECT name, age FROM users WHERE name = '张三';

避免使用 SELECT *,只选择必要的字段:

sql 复制代码
 -- 避免回表
 SELECT id, name FROM users WHERE name = '张三';
 ​
 -- 需要回表(除非索引覆盖所有字段)
 SELECT * FROM users WHERE name = '张三';

如何判断是否发生了回表

可以通过EXPLAIN命令查看执行计划,如果Extra列显示"Using index",说明使用了覆盖索引

相关推荐
Lee川2 分钟前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i2 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有2 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有3 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫4 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫4 小时前
Handler基本概念
面试
Wect4 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼5 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼5 小时前
Next.js 企业级落地
前端·javascript·面试
掘金安东尼5 小时前
React 性能优化完全指南 2026
前端·javascript·面试