MySQL 回表(覆盖索引之外的查询逻辑)完整讲解
一、什么是回表
前置基础:InnoDB 索引结构
InnoDB 主键索引叫聚簇索引 :
叶子节点 = 完整一行数据(主键+所有字段)。
普通索引(二级索引):
叶子节点 = 「索引列值 + 主键ID」,不存完整行数据。
回表定义
当SQL使用普通二级索引 查询,但SELECT/WHERE需要的字段不在二级索引里时:
- 先通过二级索引找到满足条件的主键ID;
- 拿着主键ID再去主键聚簇索引 中查询完整行数据;
这个第二次查表拿全字段 的过程,就叫回表。
举个例子
表 t_fund,主键 id,普通索引 idx_product_code(product_code)
sql
-- 会回表
SELECT product_name, net_value FROM t_fund WHERE product_code = '001';
执行流程:
- 在
idx_product_code找到product_code='001',拿到主键id=1001; - 根据
id=1001去主键索引读取product_name、net_value;
第二步操作就是回表。
无回表(覆盖索引)
如果索引包含查询全部字段,不需要二次查表,叫索引覆盖,不会回表:
sql
CREATE INDEX idx_product ON t_fund(product_code,product_name,net_value);
SELECT product_name, net_value FROM t_fund WHERE product_code = '001';
只走二级索引,一次查询完成,无回表。
二、为什么会产生回表(不是"要用",是查询逻辑必然产生)
很多人误区:不是主动要用回表,是缺少覆盖索引时自动发生,产生的原因:
- 查询字段超出二级索引存储范围
二级索引只存索引列+主键,查其他业务字段必须回主键索引拿数据; - 仅创建单列普通索引,未做联合覆盖索引
金融业务经常查多字段,单列索引必然触发回表; - 使用
SELECT *查询所有字段
无论什么二级索引,*一定会触发回表。
三、回表带来的问题(业务开发重点关注)
- 性能损耗,翻倍IO
两次索引树查找,IO次数翻倍;大数据量、日终估值批量查询会明显变慢; - 大分页、批量统计场景效率暴跌
比如估值报表批量查询持仓数据,大量回表会拉长跑批时间; - 高并发接口出现慢SQL,引发锁、超时、连接池耗尽。
四、开发中怎么规避/优化回表(金融资管平台实用方案)
- 杜绝
select *,只查询需要的字段; - 创建联合覆盖索引,把查询条件、返回字段全部放进索引;
- 统计类、报表类SQL优先设计覆盖索引,消除回表;
- 复杂汇总查询可使用临时表、物化视图提前聚合,减少实时回表查询。
五、结合你的金融资产管理平台场景举例
场景:根据产品代码查询基金净值、产品名称、持仓规模(估值报表常用)
- 只建单列索引
product_code:每次查询都回表,亿级资产数据跑批卡顿; - 建立覆盖索引
idx_pdt_code(product_code,product_name,net_value,scale):直接消除回表,批量估值、报表查询速度大幅提升。
六、补充区分
- 主键索引查询:永远不会回表,直接拿到完整行;
- 二级索引+字段不全:必然回表;
- 二级索引包含全部查询字段:覆盖索引,无回表。