在mysql的查询流程中,当使用二级索引查询数据时,如果索引中不包含查询所需的所有字段,就需要回到主键索引中查找完整行数据,这个额外的查找过程就是回表。回表会增加磁盘IO次数,直接影响查询性能,因此减少回表次数是mysql性能优化的重要方向。

回表产生的原因
mysql的索引分为主键索引(聚簇索引)和二级索引(非聚簇索引)。主键索引的叶子节点存储的是整行数据,而二级索引的叶子节点存储的是主键值。当通过二级索引查询时,如果查询的字段不在二级索引的覆盖范围内,就需要根据二级索引查到的主键值,再去主键索引中查找对应的行数据,这个步骤就是回表。
比如有一张用户表,结构如下:
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `age` int(11) NOT NULL, `email` varchar(100) NOT NULL, PRIMARY KEY (`id`), KEY `idx_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
如果执行查询SELECT id, name, age FROM user WHERE name = '张三',由于idx_name索引只包含name和id字段,不包含age字段,所以查询到name为张三的记录后,还需要根据id去主键索引中查找age字段,就会产生回表。
减少回表次数的方法
1. 使用覆盖索引
覆盖索引是指索引包含了查询所需的所有字段,这样查询时直接从索引中就能获取全部数据,不需要回表。这是减少回表最直接有效的方法。
针对上面的用户表,如果经常需要查询name和age字段,可以创建联合索引idx_name_age:
-- 创建包含name和age的联合索引 ALTER TABLE `user` ADD INDEX `idx_name_age` (`name`, `age`);
之后执行SELECT name, age FROM user WHERE name = '张三',就可以直接从idx_name_age索引中获取到所需的所有字段,不需要回表。需要注意的是,联合索引的字段顺序要符合最左前缀原则,查询条件要包含联合索引的最左字段才能生效。
2. 优化查询字段,避免使用SELECT *
很多开发者习惯写SELECT *查询所有字段,这样即使有二级索引,也几乎一定会触发回表,因为二级索引很少会包含所有字段。应该只查询需要的字段,尽量让查询字段被索引覆盖。
比如上面的场景,如果只需要id和name字段,就写SELECT id, name FROM user WHERE name = '张三',而不是SELECT * FROM user WHERE name = '张三',前者可以直接从idx_name索引中获取数据,不需要回表。
3. 合理设计索引字段
在设计索引时,要结合高频查询的场景,把查询中经常用到的字段尽量包含到索引中。如果某个字段经常被查询,且经常和其他字段一起作为查询条件或返回字段,就可以考虑将其加入到联合索引中。
比如用户表中经常需要查询name、age和email字段,就可以创建idx_name_age_email联合索引,这样相关查询都可以走覆盖索引,避免回表。
4. 利用主键索引查询
主键索引的叶子节点存储的是整行数据,所以如果查询条件使用的是主键,就不需要回表。在业务允许的情况下,可以优先使用主键作为查询条件。
比如查询某个用户的详细信息,已知用户id是100,执行SELECT * FROM user WHERE id = 100,直接走主键索引就可以获取到所有数据,不会产生回表。
其他mysql性能提升技巧
除了减少回表次数,还有以下实用的mysql性能提升方法:
- 合理选择索引类型,比如字符串字段如果长度较长,可以使用前缀索引减少索引存储空间,提升查询效率
- 避免在索引字段上使用函数或表达式,否则索引会失效,导致全表扫描
- 控制单表数据量,当单表数据量过大时,可以考虑分库分表,减少单表查询的压力
- 优化查询语句,避免不必要的关联查询,减少子查询的使用,尽量使用小表驱动大表
- 合理配置mysql参数,比如调整
innodb_buffer_pool_size,让更多的数据和索引缓存在内存中,减少磁盘IO
回表次数验证方法
可以通过EXPLAIN命令查看查询语句的执行计划,判断是否存在回表。如果执行计划的Extra列显示Using index,说明使用了覆盖索引,没有回表;如果显示NULL或者没有其他覆盖索引相关的提示,说明存在回表。
比如执行以下语句查看之前的查询:
EXPLAIN SELECT id, name FROM user WHERE name = '张三';
如果Extra列显示Using index,说明该查询没有回表;如果显示其他内容,说明存在回表,需要调整索引或查询语句。