Mysql:什么情况下会出现“回表”?

你好,我是风一样的树懒,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。

点击下方👇关注公众号,带你一起复习后端技术,看看面试考点,补充积累技术知识,每天都为面试准备积累


文章可能会比较长,主要解析的非常详解,或涉及一些底层知识,供面试高阶难度用。可以根据自己实际理解情况合理取舍阅读


01
什么是回表?


“回表”是指在使用索引查询时,MySQL 会通过索引快速找到符合条件的记录的 ID(主键)或索引列的值,但为了获取其他列的详细信息,MySQL 需要使用这些 ID 或索引列的值 再次回到原始表中查找完整的记录。这一过程就叫做“回表”。如果查询结果中涉及到非索引列的数据,MySQL 就需要通过索引值再回到原始数据表中进行查找。

回表的具体过程:

查询过程:当执行查询时,MySQL 通过索引定位到符合条件的记录位置(比如根据索引列找到一条记录的主键)。

回表查找数据:然后,MySQL 会根据这些主键值去数据表中查找完整的记录,拿到完整的字段值。

02
什么情况下会出现“回表”?


查询的字段不在索引中

如果查询的字段不在索引中,那么 MySQL 会先通过索引找到符合条件的行,然后回到原表中去查询其他字段的值。这是最常见的回表情况。

假设有一个 users 表,如下所示:

CREATE TABLE users (    id INT PRIMARY KEY,    name VARCHAR(255),    email VARCHAR(255),    age INT);

假设我们创建了一个针对 name 字段的索引:

CREATE INDEX idx_name ON users(name);

如果你查询 name 和 email 字段:

SELECT name, email FROM users WHERE name = 'John';

在这个查询中,name 字段有索引,MySQL 会通过 idx_name 索引查找到符合条件的记录。但是因为 email 字段没有包含在索引中,MySQL 需要回表查询完整的 email 数据。

回表步骤:

  • MySQL 通过 idx_name 索引查找符合条件的 name = 'John' 的记录,假设找到了对应的 id。

  • 然后,MySQL 通过 id 这个主键值回到原始的 users 表中查询 email 字段的值。

复合索引不覆盖查询字段
如果你使用的是复合索引(多个列组合的索引),但查询中所使用的字段没有完全覆盖索引,那么 MySQL 仍然会回表。

例子:

假设你创建了一个包含 name 和 age 的复合索引:

CREATE INDEX idx_name_age ON users(name, age);

然后执行如下查询:

SELECT name, email FROM users WHERE name = 'John' AND age = 25;

这个查询中,name 和 age 都包含在 idx_name_age 复合索引中,所以 MySQL 可以快速定位到符合条件的记录。然而,由于查询的字段 email 不在索引中,MySQL 仍然需要回表查询。

查询条件不完全匹配索引
如果查询条件只包含索引的一部分列,MySQL 可能无法直接通过索引查询到所有需要的数据,依然需要回表。

例子:

假设你有以下复合索引:

CREATE INDEX idx_name_age ON users(name, age);

然后执行如下查询:

SELECT name FROM users WHERE age = 25;

在这个查询中,索引是基于 (name, age) 组合的,但查询条件中只使用了 age 字段。因此,MySQL 只能通过索引的部分列来定位符合条件的记录,但由于查询结果中不包含 name 以外的其他列(例如 age),所以必须回表去读取完整的记录。

03
如何避免回表?


覆盖索引(Covering Index)

覆盖索引是指索引包含了查询所需的所有字段。在这种情况下,查询结果可以直接从索引中获取,而不需要回表,从而避免了回表带来的性能损失。

例子:

假设你只查询 name 和 email,并且 name 和 email 都在一个复合索引中:

CREATE INDEX idx_name_email ON users(name, email);

然后执行如下查询:

SELECT name, email FROM users WHERE name = 'John';

由于 name 和 email 都在 idx_name_email 索引中,查询可以通过索引直接返回结果,而不需要回表。

选择合适的索引列
在设计索引时,要确保索引尽可能覆盖常用的查询字段,避免在查询时出现需要回表的情况。

例子:

如果你经常按 name 和 email 查询,可以创建一个复合索引:

CREATE INDEX idx_name_email ON users(name, email);

避免不必要的查询字段
只查询需要的字段,尽量避免使用 SELECT *,这样可以减少回表的概率。

04
总结


回表是当查询中的字段没有包含在索引中时,MySQL 在通过索引查找到符合条件的记录后,再次回到原始表中查询完整的数据。这会导致性能损失,因为需要额外的磁盘 I/O 操作。

为了避免回表,应该:

  • 使用覆盖索引,即索引包含查询所需的所有字段。

  • 精确选择索引列,确保查询条件能够完全利用索引。

  • 避免不必要的字段查询,减少不必要的回表操作。


今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!

END


扫码关注

一起积累后端知识
不积跬步,无以至千里
不积小流,无以成江海

喜欢此内容的人还喜欢

《Java面试题指南》回归啦~


一个阿里二面面试官必问的问题


Lambda表达式说爱你不容易


分享面试:mysql数据库索引失效的情况


Spring-Boot中一个不起眼的好工具StopWatch