从索引到数据表:回表操作的实战解析和优化策略

250 阅读5分钟
  • ImnnoDB 中,索引B+ Tree的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引。
  • 而索引B+ Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引。

存储数据方面

主键(聚簇)索引的B+树的叶子节点直接就是我们要查询的整行数据了。非主键(非聚簇)索引的叶子节点是主键的值。

什么是回表?

用通俗的语言来说,回表是数据库查询中的一种过程。它发生在你使用索引查询数据时,但索引中不包含查询所需的所有字段,这时数据库需要再次回到数据表中查找字段的完整数据。

当我们根据非聚簇索引查询的时候,会先通过非聚簇索引查到主键的值,之后,再通过主键的值再进行一次查询才能得到我们要查询的数据。而这个过程就叫做回表。

mermaid.png

打个比方:

  1. 你在图书馆找一本书(比如《西游记》),图书馆有一本“目录”(索引)。
  2. 目录中可以快速告诉你书的位置,比如“书架A层第5本”。
  3. 但是,目录里只记录了书名,没有书的详细内容。如果你还想看书的内容,就需要回到书架上去取那本书。

数据库中的“回表”就是类似的过程:

  • 索引只是记录了部分信息,帮你快速找到位置。
  • 数据表里保存了完整的记录。如果查询需要访问索引中没有的信息,就得“回到表中”去查找,称为“回表”。

回表的工作原理

  1. 索引中查找:数据库会先利用索引找到符合条件的数据的“位置”(通常是主键值)。
  2. 回表取数据:根据主键值,再回到数据表中取出完整的记录。

例如: 假设有一个用户表 User,字段如下:

idnameemailage
1张三zhangsan@example.com25
2李四lisi@example.com30
3王五wangwu@example.com28

如果你在 name 字段上建立了索引:

CREATE INDEX idx_name ON User (name);

现在执行查询:

SELECT email FROM User WHERE name = '张三';
  • 第一步:数据库会利用 name 索引快速找到“张三”对应的主键 id=1
  • 第二步:但因为 email 不在索引中,数据库需要“回到表里”,根据 id=1 找到对应的 email 数据。

这种额外的“回表”操作可能会导致查询效率变低,尤其当表数据量很大时。


怎么减少回表的次数?

既然回表会增加查询成本,我们可以通过以下几种方法减少它的发生:

1. 覆盖索引

如果索引中已经包含了查询所需的所有字段,就不需要回表了。这种情况被称为覆盖索引

继续上面的例子: 如果你经常查询 nameemail,你可以建立一个包含多个字段的复合索引:

CREATE INDEX idx_name_email ON User (name, email);

现在执行:

SELECT email FROM User WHERE name = '张三';

此时,数据库会直接从 idx_name_email 索引中找到 nameemail 的值,无需回表,查询效率大大提升!

2. 只查询索引字段

如果你的查询只涉及索引字段,就完全不需要回表。例如:

SELECT name FROM User WHERE name = '李四';

因为 name 本身就是索引字段,数据库只需要从索引中查找即可,无需回表。

3. 选择合适的索引字段

在设计索引时,尽量将经常被查询的字段加入索引,或者使用复合索引。如果查询时总是需要某些特定字段,那么把这些字段包括进索引中可以减少回表的可能性。

4. 避免大范围查询

如果查询条件返回的数据量过大,回表的次数也会随之增加(因为每条记录都需要回表一次)。优化查询条件,减少结果集,可以有效减少回表次数。

5. 合理选择主键

MySQL InnoDB 存储引擎中,所有的二级索引(非主键索引)都指向主键。如果你的主键很大,比如用一个很长的字符串作为主键,那么回表操作会更耗时。合理选择主键,可以减少回表的开销。


举个实际案例

假设有一个订单表 Orders,字段如下:

order_iduser_idstatustotal_pricecreated_at
10011paid200.002023-09-01 12:00
10022pending150.002023-09-02 14:30
10033paid300.002023-09-03 10:00

如果你经常查询订单的状态和金额:

SELECT total_price FROM Orders WHERE status = 'paid';

你可以创建一个复合索引来覆盖这个查询:

CREATE INDEX idx_status_total ON Orders (status, total_price);

这样,查询时会直接从 idx_status_total 索引中获取数据,无需回表。


总结

回表是数据库为了获取完整数据而做的额外操作,虽然它是正常的行为,但可能会降低查询效率。减少回表次数的方法包括:

  1. 使用覆盖索引。
  2. 只查询索引字段。
  3. 合理设计复合索引。
  4. 避免大范围查询。
  5. 优化主键设计。

通过这些优化手段,可以大大提高数据库的查询性能,让你的系统更加高效!