SQL

慢sql优化(1):limit下desc和asc性能相差百倍

2022-09-20  本文已影响0人  小胖学编程

背景:Task任务,需要将最近一天的数据查询出来,然后同步到kafka中。

一次慢查询的优化

环境:mysql5.7.26
表数据总量:4千万数据
单日数据量:1百万数据
表索引:date_user_id_idx(date、user_id)
主键索引:id
需求:需要将60天的数据归到到历史表,需要将昨天的数据同步给kafka。
sql:select * from tb_temp where date = 20220919

V1升级(分批次查询)

优化方案:限制每次查询的数量,分多次查出。

此时我们一般会使用分页查询。

select * from  tb_temp where date = 20220919 and id<=xxx order by id desc limit 5000

思考点:为什么要专门引入order by id desc

需要注意的时候,因为select * from tb_temp where date = 20220919命中的是date_user_id_idx索引。联合索引的规则:先根据date排序、然后根据user_id排序。所以这条只查询date返回的主键id是无序的。而游标分页需要一个有序的游标id,我们既然要借助主键id作为游标id,所以需要专门引入order by id desc

思考点:为什么引入order by id desc后,sql执行计划从date_user_id_idx变为主键索引

如果命中date_user_id_idx二级索引的话,我们得到的二级索引下的id是无序的。那么会带来回表查询+文件排序(100w数据)的性能损耗。那么在mysql看来直接走id主键索引性能会更优。

V2升级(limit底层原理)

而我们采用的正是V1版本的升级方案(游标法),但代码执行过程依旧会存在两种慢sql。

慢sql-1(归档历史数据):select * from tb_temp where date = 20220723 and id<=9223372036854775807(因为第一次的的时候给框架默认游标是Long.MAX) order by id desc limit 5000

慢sql-2(同步昨日数据):select * from tb_temp where date = 20220921 and id<=12344566(“临界数据的id”) order by id desc limit 5000

limit 200执行原理:

  1. 在表中拉取到200条数据后,终止;
  2. 扫描完where条件规定的返回后,终止;

当查询出的行数无法满足limit的限制时,mysql需要将where范围内的数据全部扫描完,流程才会被结束。

那么我们可以看到:

慢sql1(归档历史数据):由于是第一次查询所以框架给的游标id是默认值也就是Long的最大值。而0723的数据分布在聚簇索引树最左侧,本次查询就会在聚簇索引树的最右侧发起查询。期间会涉及到大量的IO操作,导致我们查询很慢。但是(归档历史数据)的sql第二次查询的时候:select * from tb_temp where date = 20220723 and id<=111122002 order by id desc limit 5000已经确定了范围,就会导致查询非常快速。

慢sql1(同步昨日数据):20220921数据分布在聚簇索引树的最右侧,所以开始的时候查询效率很高,但是到了20220921数据的边界处时,只查询到了288条数据,没有满足5000条数据的终止条件,且我们并没有给出终止条件(date不是终止条件,而是筛选条件)所以依旧会一路去左查询满足条件的数据,直到遍历完全表数据。

优化方案:

mysql的id是自增主键,逻辑上我们认为date和id是有关系的。所以需要查询当天的第一笔或者最后一笔id,作为终止limit的条件。

以同步昨日数据为例:

-- 先查询上一天的最后一笔数据的lastId
select id from tb_temp where date = 20220920 order by id desc limit 1;

- 再将这笔id代入到下面sql中
elect * from tb_temp where date = 20220921 and id<=12344566 and id>lastId order by id desc limit 5000

彩蛋:

为什么要查询上一天最后一笔数据的id,而不是当天的第一笔id,其实也和上面讲的有关系。

如果查询20220921的第一笔id的话(select id from tb_temp where date = 20220921 order by id limit 1)那么mysql会从聚簇索引树的最左侧开始寻找(而实际上这笔数据位于最右侧,从而带来很多无用的性能开销)。

相关文章

MySQL正序和倒序排序思考

上一篇下一篇

猜你喜欢

热点阅读