- 在
ImnnoDB中,索引B+ Tree的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引。 - 而索引
B+ Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引。
存储数据方面
主键(聚簇)索引的B+树的叶子节点直接就是我们要查询的整行数据了。非主键(非聚簇)索引的叶子节点是主键的值。
什么是回表?
用通俗的语言来说,回表是数据库查询中的一种过程。它发生在你使用索引查询数据时,但索引中不包含查询所需的所有字段,这时数据库需要再次回到数据表中查找字段的完整数据。
当我们根据非聚簇索引查询的时候,会先通过非聚簇索引查到主键的值,之后,再通过主键的值再进行一次查询才能得到我们要查询的数据。而这个过程就叫做回表。
打个比方:
- 你在图书馆找一本书(比如《西游记》),图书馆有一本“目录”(索引)。
- 目录中可以快速告诉你书的位置,比如“书架A层第5本”。
- 但是,目录里只记录了书名,没有书的详细内容。如果你还想看书的内容,就需要回到书架上去取那本书。
数据库中的“回表”就是类似的过程:
- 索引只是记录了部分信息,帮你快速找到位置。
- 数据表里保存了完整的记录。如果查询需要访问索引中没有的信息,就得“回到表中”去查找,称为“回表”。
回表的工作原理
- 索引中查找:数据库会先利用索引找到符合条件的数据的“位置”(通常是主键值)。
- 回表取数据:根据主键值,再回到数据表中取出完整的记录。
例如:
假设有一个用户表 User,字段如下:
| id | name | age | |
|---|---|---|---|
| 1 | 张三 | zhangsan@example.com | 25 |
| 2 | 李四 | lisi@example.com | 30 |
| 3 | 王五 | wangwu@example.com | 28 |
如果你在 name 字段上建立了索引:
CREATE INDEX idx_name ON User (name);
现在执行查询:
SELECT email FROM User WHERE name = '张三';
- 第一步:数据库会利用
name索引快速找到“张三”对应的主键id=1。 - 第二步:但因为
email不在索引中,数据库需要“回到表里”,根据id=1找到对应的email数据。
这种额外的“回表”操作可能会导致查询效率变低,尤其当表数据量很大时。
怎么减少回表的次数?
既然回表会增加查询成本,我们可以通过以下几种方法减少它的发生:
1. 覆盖索引
如果索引中已经包含了查询所需的所有字段,就不需要回表了。这种情况被称为覆盖索引。
继续上面的例子:
如果你经常查询 name 和 email,你可以建立一个包含多个字段的复合索引:
CREATE INDEX idx_name_email ON User (name, email);
现在执行:
SELECT email FROM User WHERE name = '张三';
此时,数据库会直接从 idx_name_email 索引中找到 name 和 email 的值,无需回表,查询效率大大提升!
2. 只查询索引字段
如果你的查询只涉及索引字段,就完全不需要回表。例如:
SELECT name FROM User WHERE name = '李四';
因为 name 本身就是索引字段,数据库只需要从索引中查找即可,无需回表。
3. 选择合适的索引字段
在设计索引时,尽量将经常被查询的字段加入索引,或者使用复合索引。如果查询时总是需要某些特定字段,那么把这些字段包括进索引中可以减少回表的可能性。
4. 避免大范围查询
如果查询条件返回的数据量过大,回表的次数也会随之增加(因为每条记录都需要回表一次)。优化查询条件,减少结果集,可以有效减少回表次数。
5. 合理选择主键
MySQL InnoDB 存储引擎中,所有的二级索引(非主键索引)都指向主键。如果你的主键很大,比如用一个很长的字符串作为主键,那么回表操作会更耗时。合理选择主键,可以减少回表的开销。
举个实际案例
假设有一个订单表 Orders,字段如下:
| order_id | user_id | status | total_price | created_at |
|---|---|---|---|---|
| 1001 | 1 | paid | 200.00 | 2023-09-01 12:00 |
| 1002 | 2 | pending | 150.00 | 2023-09-02 14:30 |
| 1003 | 3 | paid | 300.00 | 2023-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 索引中获取数据,无需回表。
总结
回表是数据库为了获取完整数据而做的额外操作,虽然它是正常的行为,但可能会降低查询效率。减少回表次数的方法包括:
- 使用覆盖索引。
- 只查询索引字段。
- 合理设计复合索引。
- 避免大范围查询。
- 优化主键设计。
通过这些优化手段,可以大大提高数据库的查询性能,让你的系统更加高效!