减少数据回表常见的三种方式分别是1)查询条件使用聚集索引;2)使用索引下推;3)使用索引覆盖。
按照索引使用数据结构B+树叶子结点是否包含表中全部字段,mysql 索引可以分为聚集索引和非聚集索引(辅助索引),聚集索引反应了记录在磁盘上存储的物理结构,辅助索引反应了记录的逻辑结构。聚集索引索引B+树叶子结点存储表所有字段属于聚集索引,查询索引可以直接获得所有字段信息,索引B+树叶子结点只存储聚集索引id,根据辅助索引查询全部字段时,首先根据辅助索引,查询到聚集索引,然后根据查询到的聚集索引id逐个回表查询记录完整信息。综上,使用聚集索引查询不用二次回表。
先有如下一个订单表,考虑需要经常查询某个用户购买某个商品的信息,所以设计了联合主键(user_id, spu_id)。
CREATE TABLE `order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
`spu_id` bigint(20) unsigned NOT NULL COMMENT 'SPUID',
...
KEY `idx_userId_spuId` (`userId`,`spu_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单';
现需要查询一个用户下的所有spu_id:
select * from `order` where `user_id` = 123456; -- 方式1,从表中查询整条记录,然后在后端代码中提取spu_id
select `spu_id` from `order` where `user_id` = 123456; -- 方式2,只查询spu_id字段
相同点是,在数据量很大情况下,两种方式都会走索引idx_userId_spuId;不同点是,方式1,先根据辅助索引找到主键id(此时聚集索引建立在主键上),然后对每个主键id进行回表查询整条记录信息,方式2,因为联合索引恰好包含查询字段spu_id,避免了二次回表查询。
下面是一个用户表,同时在用户姓名和年龄创建了一个联合索引
CREATE TABLE `user_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(512) unsigned NOT NULL COMMENT '姓名',
`age` bigint(20) unsigned NOT NULL COMMENT '年龄',
...
KEY `idx_name_age` (`name`,`age`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息';
先需要查询18岁以下所有姓张的同学信息(场景好奇快):
select * from `user_info` where name like '张' and age < 18;
在mysql 5.6 之前,查询过程使用idx_name_age索引查询主键id,然后根据主键逐个回表(没在联合索引上使用age < 8条件,是使用联合索引字段作为查询条件,遇到非等值查询联合索引后续字段会失效),同时使用age < 8条件进行过滤,回表记录可能会因为age条件而在最终结果中过滤掉,这样,其实被过滤的记录要是不用回表可以减少二次回表次数,提高查询效率。在mysql 5.7 引入「索引下推」,查询过程变成,name走索引idx_name_age,同时继续直接使用age对索引进行过滤,需要回表查询的小部分记录都会包含在最终结果里面。
note:order by 中排序字段可以和where中查询字段都被包含在联合索引中,也可能引起索引下推。
1 Mysql优化(十)什么是 MySQL 的 回表 ?怎么减少回表的次数?
2 MySQL聚集索引和非聚集索引
3 主键,聚集索引,不是一个东西!
4 Mysql性能优化:什么是索引下推?