Sql优化之回表

京东云开发者
• 阅读 352

前言:

MySQL的性能是大家在使用时十分关心的问题,比如在高并发访问时,并且有慢sql存在的情况下,MySQL的性能会明显下降,这会导致数据库响应时间变慢,甚至导致数据库宕机。那么为了避免Mysql性能问题,比较常用的方式创建适当的索引,提升sql语句的执行效率。而本文简单介绍一下和索引有关的回表,从实际案例出发,讲讲什么是回表,如何避免回表,如何减少回表。

实际案例:

前置仓产能动态ETA时效降级需求,提供一个数据同步接口,接送大数据同步过来的前置仓的仓负债和配负债,需要支持批量,最多可支持10个批量。

表结构如下,可以看到唯一索引是 UNIQUE KEY uniq_shop (shop_id,shop_type)

CREATE TABLE `shop_load_degrade_strategy` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `shop_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '门店ID',
  `shop_type` int(11) NOT NULL DEFAULT '0' COMMENT '门店类型',
  `current_store_load` int(10) NOT NULL DEFAULT '0' COMMENT '当前仓负载',
  `active_store_load` int(10) NOT NULL DEFAULT '0' COMMENT '正在使用的仓负载',
  `active_store_degrade_delay` int(11) NOT NULL DEFAULT '0' COMMENT '正在使用的仓降级时间分钟数',
  `current_delivery_load` int(10) NOT NULL DEFAULT '0' COMMENT '当前配送负载',
  `active_delivery_load` int(10) NOT NULL DEFAULT '0' COMMENT '正在使用的配送负载',
  `active_delivery_degrade_delay` int(11) NOT NULL DEFAULT '0' COMMENT '正在使用的配送降级时间分钟数',
  `current_load_active_auto` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '仓配负载自动生效,1开启',
  `enable` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否开启,1开启',
  `artificial_active_time` datetime DEFAULT NULL COMMENT '运营手动生效时间',
  `active_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后生效时间',
  `approver` varchar(100) DEFAULT NULL COMMENT '审批人',
  `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:1启用,0失效',
  `yn` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '1删除',
  `update_pin` varchar(50) NOT NULL DEFAULT '' COMMENT '最后修改人',
  `update_time` datetime NOT NULL COMMENT '修改时间',
  `create_pin` varchar(50) NOT NULL DEFAULT '' COMMENT '创建人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `ts` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '时间戳',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_shop` (`shop_id`,`shop_type`),
  KEY `idx_active_time` (`active_time`),
  KEY `idx_update_pin` (`update_pin`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='门店负载降级策略表';

数据处理流程



Sql优化之回表







最简单的处理方式就是,逐条数据处理

public void handRecords(List<ShopLoadDegradeStrategy> records){
    for(ShopLoadDegradeStrategy shopLoadDegradeStrategy: records){
        dbShopLoadDegradeStrategy = findByshopIdAndShopType(shopLoadDegradeStrategy);
        if(null != dbShopLoadDegradeStrategy)
            shopLoadDegradeStrategy.setId(dbShopLoadDegradeStrategy.getId())            
            updateById(shopLoadDegradeStrategy);
        }else{
            insert(shopLoadDegradeStrategy);
        }
    }
}

最初的优化思想的减少网络传输次数

public void handRecords(List<ShopLoadDegradeStrategy> records){
   //批量查询
   List<ShopLoadDegradeStrategy> dbShopLoadDegradeStrategys = findAllByshopIdAndShopTypeList(records);
   updateShopLoadDegradeStrategys,insertShopLoadDegradeStrategys = handleShopLoadDegradeStrategys(records,dbShopLoadDegradeStrategys);
   updateForEach(updateShopLoadDegradeStrategys);
   //批量插入
   batchInsert(insertShopLoadDegradeStrategys);
}

具体sql语句:

批量按照门店Id和门店类型查询数据是否存在

select * from shop_load_degrade_strategy where (shop_id=? and shop_type=?) or (shop_id=? and shop_type=?)

存在更新数据, 因为shop_id和shop_type是组合的唯一索引,所以按照主键更新和按照唯一索引更新的结果是一样的。但是过程真的一样吗?

update shop_load_degrade_strategy set current_store_load=?,current_delivery_load=?  where shop_id=? and shop_type=?

看是很简单的两个sql语句,explain分析sql执行计划,可以看出用到了索引,并且sql语句执行也很快,不存在慢sql的问题。还有优化的空间吗?



优化过程分析

先简单介绍一下索引按物理存储分类

mysql的B+树

1)聚集索引(聚簇索引) InnoDB的聚簇索引就是按照主键顺序构建 B+Tree结构。叶子节点存储数据行的数据。



Sql优化之回表



2)辅助索引(二级索引) InnoDB的辅助索引就是按照索引列构建 B+Tree结构。叶子节点存储的是索引列的值以及对应行的主键值



Sql优化之回表



覆盖索引并不是说是索引结构,覆盖索引是一种很常用的优化手段。因为在使用辅助索引的时候,我们只可以拿到主键值,相当于获取数据还需要再根据主键查询主键索引再获取到数据。但是试想下这么一种情况,在上面shop_load_degrade_strategy表中的组合索引查询时,如果我只需要id,shop_id,shop_type字段的,那是不是意味着我们查询到组合索引的叶子节点就可以直接返回了,而不需要回表。这种情况就是覆盖索引。

查询sql语句优化为:

select id,shop_id,shop_type from shop_load_degrade_strategy where (shop_id=? and shop_type=?) or (shop_id=? and shop_type=?)

优化前
1.select * from shop_load_degrade_strategy where (shop_id=? and shop_type=?) or (shop_id=? and shop_type=?)

优化前:

1.先查询辅助索引找到对应的主键Id

2.然后通过id回表查询数据表的数据。



Sql优化之回表



优化后

1.只需要查询辅助索引就可以,通过查询辅助索引就可以查询到所以需要的列,想要使用覆盖索引,查询的结果的列只能包含在辅助索引列和主键索引列。通过查看sql计划可以到Extra的值为Using index。



Sql优化之回表



总结:优化前要查询除了索引字段(shop_id,shop_type)和主键(id)之外的字段,不能用到覆盖索引这种方式。所以在编写sql的时候,查询的数据列一定是满足业务的最小列集合,首先减少网络传输的数据量,同时也会减少应用的内存使用,还有看看能不能使用到覆盖索引这种方式进行优化,有些情况下甚至会在辅助索引中增加联合索引的列来使用到覆盖索引减少回表。



更新sql语句的优化

优化后:
update shop_load_degrade_strategy set current_store_load=?,current_delivery_load=?  where id=?
优化前:
update shop_load_degrade_strategy set current_store_load=?,current_delivery_load=?  where shop_id=? and shop_type=?

优化前的更新语句虽然使用到了辅助索引作为查询条件来进行数据更新,但是mysql执行sql的过程为:

1.先查询辅助索引找到索引对应的主键Id

2.然后通过id回表查询数据表的数据。

3.最后更新数据。

优化后的sql执行过程为:

1.通过id回表查询数据表的数据。

2.最会更新对应id的数据,





从以上描述可以看出,减少回表也是我们优化sql语句的一种很重要的思想。通过减少回表这个思想和处理数据的方式,引出说明一下mysql索引下推这个概念,其核心思想就是减少回表

什么是索引下推

索引下推(Index Condition Pushdown,索引条件下推,简称ICP),是MySQL5.6版本的新特性,它可以在对联合索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,能有效的减少回表次数(目前我们使用的mysql版本较高,一般大家可能感觉这是正常的,但是mysql5.6之前都不是这样实现的,下面会细细道来)。



适用条件

我们先来了解一下索引下推的使用条件及限制:

•只支持select。

•当需要访问全表时,ICP用于range,ref,eq_ref和ref_or_null访问类型。

•ICP可用于InnoDB和MyISAM表,包括分区的InnoDB和MyISAM表。(5.6版本不适用分区表查询,5.7版本后可以用于分区表查询)。

•对于InnDB引擎只适用于二级索引(也叫辅助索引),因为InnDB的聚簇索引会将整行数据读到InnDB的缓冲区,这样一来索引条件下推的主要目的减少IO次数就失去了意义。因为数据已经在内存中了,不再需要去读取了。

•在虚拟生成列上创建的辅助索引不支持ICP(注:InnoDB支持虚拟生成列的辅助索引)。

•使用了子查询的条件无法下推。

•使用存储过程或函数的条件无法下推(因为因为存储引擎没有调用存储过程或函数的能力)。

原理介绍

1.未使用ICP的情况下:

•存储引擎读取索引记录;

•根据索引中的主键值,定位并读取完整的行记录;

•存储引擎把记录交给Server层去检测该记录是否满足WHERE条件。



1.使用ICP的情况下:

•存储引擎读取索引记录(不是完整的行记录);

•判断WHERE条件部分能否用索引中的列来做检查,条件不满足,则处理下一行索引记录;

•条件满足,使用索引中的主键去定位并读取完整的行记录(就是所谓的回表);

•存储引擎把记录交给Server层,Server层检测该记录是否满足WHERE条件的其余部分。



具体例子

聚簇索引



Sql优化之回表



辅助索引



Sql优化之回表





假设有一条查询语句

select * from shop_load_degrade_strategy where shop_id <102 and shop_type=1;

根据索引最左匹配原则,上面这个sql语句在查索引树的时候,只能用102,查到第一个满足条件的记录:id为7,8两条记录。



那接下来我们具体看一下 使用与未使用ICP的情况。

未使用ICP情况下:

1.存储引擎根据联合索引先找到shop_id<102 的主键id(7、8),再逐一进行回表扫描,去聚簇索引找到完整的行记录,再根据条件shop_type=1对拿到的数据进行筛,可以看到有两次回表。

使用ICP情况下:

1.存储引擎会根据(shop_id,shop_type)联合索引,找到shop_id <102,由于联合索引中包含shop_type

列,所以存储引擎直接再联合索引里按照条件shop_type=1进行过滤,就只会找到主键id(7),然后根据过滤后的数据再依次进行回表扫描。可以看到只有一次回表。通过sql执行计划可以看出 Extra的值为“Using index condition”表示使用了索引下推。



Sql优化之回表





结语:

回表操作:当所要查找的字段不在非主键索引树上时,需要通过叶子节点的主键值去主键索引上获取对应的行数据,这个过程称为回表操作,可以看出Mysql通过索引下推来减少回表次数,从而提高sql性能。所以在项目开发编写sql语句的时候,要时刻注意是否可以通过减少回表或者不回表来优化sql性能,减少Mysql数据性能的压力。

参考文献:

https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html

点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
sql执行计划与优化
在我们实际工作中大部分人会遇到sql优化的问题,这篇文章主要介绍SQL优化相关。首先我们怎么发现我们的sql执行效率低呢,最简单的方法就是当用户反馈慢的时候我们就会知道哪里可能会有sql效率影响的问题,这里排除其他影响情况,只考虑数据库sql慢的问题。当然这种方式对于我们来说很被动,我们还可以通过什么方式找到有性能问题sql,我们可以通过MySQL的配置文
Wesley13 Wesley13
3年前
MySQL——性能优化
性能优化的思路1、首先需要使用慢查询功能,去获取所有查询时间比较长的SQL语句。MySQL——慢查询2、其次使用explain命令去查看有问题的SQL的执行计划。MySQL——执行计划EXPLAIN3、最后可以使用showprofile\s\查看有问题的SQL的性能使用情况。MySQL高级:showprofile
Stella981 Stella981
3年前
Discuz 7.2坑爹集锦
Discuz7.2坑爹集锦SQL篇DZ使用的是MySQL的MyISAM引擎,特点是简单快速,非常适合网络扁平数据。当数据量超过一定规模(大概300万),数据关联复杂(表连接增多)后性能急剧下降。并且在高读写并发时锁表严重(MyISAM是表锁,InnoDB有行锁),甚至导致表损坏。DZ7.2代码中SQL写法存在不标准的问题,虽然不影响执行但对维
Wesley13 Wesley13
3年前
560字带你彻底搞懂:MySQL的索引优化分析
正文一、SQL分析性能下降、SQL慢、执行时间长、等待时间长查询语句写得差索引失效关联查询太多join(设计缺陷)单值索引:在user表中给name属性创建索引,createindexidx\_nameonu
Wesley13 Wesley13
3年前
Mysql 执行计划各列释义
在排查编写Mysql查询语句时,除了需要满足业务条件,还需要考虑所编写SQL的性能表现,避免出现慢SQL导致大量慢查询的情况。通常,可以通过查看执行计划的方式查看所编写SQL语句的性能优劣。此外,还可以通过查看语句的分阶段执行的时间、操作消耗来进行补充分析。1\.执行计划的列1.1.id列查询的序号1.2.s
Wesley13 Wesley13
3年前
mySql索引优化分析
MySQL索引优化分析为什么你写的sql查询慢?为什么你建的索引常失效?通过本章内容,你将学会MySQL性能下降的原因,索引的简介,索引创建的原则,explain命令的使用,以及explain输出字段的意义。助你了解索引,分析索引,使用索引,从而写出更高性能的sql语句。还在等啥子?撸起袖子就是干!案例分析我们先
Wesley13 Wesley13
3年前
oracle 优化方法总结
分析和优化的基本步骤如下:1、如果是SQL语句的写法问题,我们可以通过在不更改业务逻辑的情况下改写SQL来加以解决;2、如果是不必要的全表扫描/排序而导致了目标SQL的性能问题,我们可以通过建立合适的索引(包括函数索引、位图索引等)来加以解决;3、如果是表或者索引的不良设计导致的目标SQL的性能问题,我们可以通过重新设计表/索引
Wesley13 Wesley13
3年前
MySQL 的索引是什么?怎么优化?
索引类似大学图书馆建书目索引,可以提高数据检索的效率,降低数据库的IO成本。MySQL在300万条记录左右性能开始逐渐下降,虽然官方文档说500~800w记录,所以大数据量建立索引是非常有必要的。MySQL提供了Explain,用于显示SQL执行的详细信息,可以进行索引的优化。一、导致SQL执行慢的原因
Wesley13 Wesley13
3年前
MySQL性能优化的最佳20+条经验
今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显。关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我们程序员需要去关注的事情。当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能。这里,我们不会讲过多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库。希望下面
融云IM即时通讯 融云IM即时通讯
1个月前
融云IM干货丨 在优化IM服务API接口时,有哪些常见的性能瓶颈?
在优化IM服务API接口时,常见的性能瓶颈主要包括以下几个方面:数据库瓶颈:SQL查询过慢:数据库中的SQL查询没有经过优化,查询复杂,索引设计不合理,或者需要对大量数据进行扫描,导致数据库响应变慢。数据库连接池耗尽:在高并发请求场景下,数据库连接池中的连