【知识积累】关于解决生产Limit导出重复数据的心路历程

一、现象

用户大量导出数据,发现数据总量是对的,但是有大量重复数据,且重复的有规律。

二、实现逻辑

分区表,id增序,范围分区(less than),分区字段date,保留三十个分区的数据。

使用了PageHelper

优化思路:使用拦截器,拦截limit sql进行改写

原sql:

select * from table_name where filed1 = '' and filed2 = '' and date >= 'startDate' and date <= 'endDate' limit offset,10000

优化sql:

select * from table_name where filed1 = '' and filed2 = '' and date >= 'startDate' and date <= 'endDate' and id >= (select id from table_name where filed1 = '' and filed2 = '' and date >= 'startDate' and date <= 'endDate' order by id limit offset, 1) limit 10000

通过子查询获取每次分页的第一条记录的id,然后往后查询10000条记录

利用的是覆盖索引的方式,减少了回表的性能损耗。

三、头脑风暴

1、有个同事说limit A,B的用法错误,A是pageSize,B是offset

在公司私有云查询每次查询的数据的id是一样的,这样就证明了他的想法,他的解释是偏移量始终是B,也就是1(不变),每次取A条记录,往后取10000,不管你的A如何变化,最后每次取的都是第一条记录的id。

测试发现,公司私有云不支持偏移量,每次返回的第一个id就是一样的,这也是为什么会有后面的2、3、4、5、6.....

关于limit有两种用法:

a、limit A, B

b、limit B offset A

A:表示偏移量

B:表示pageSize

2、怀疑offset计算错误

测试环境百万数据导出,没有重复

3、怀疑mysql优化器没有走主键索引

生产执行explain,索引没有问题

4、怀疑mysql缓存、版本

查看开发环境、测试环境、生成环境的mysql版本,以及缓存的参数,是一致的

5、怀疑mybatis缓存

查看mybatis源码,二级缓存没有开启,一级缓存计算cacheKey使用了参数这一部分作为生成条件,排除

6、拿到生产环境每次导出的sql,让运维同学去导出数据,并查看响应的执行计划

导出的数据没有重复,且执行计划没有问题

四、最后的办法,导出生产环境对应分区的数据,到开发环境测试

最后发现8月3号的数据的max id比8月1号的数据的max id要小,上游在推送数据的时候,采用的方案是每天清空数据,全量推送,不保证分区顺序,8月3号的数据推送的时间比8月1号要早,造成了分区内部id有序,整体分区id无序。

当然,还发现了一个其他的问题,也就是我们只保留30个分区,也就是30天的数据,使用范围分区(less than),没有命中的分区数据,默认归到第一个分区,也就是date小于第一个分区日期的数据,全部插入到第一个分区,造成第一个分区几千万数据,其他分区几十万数据。

五、mysql的执行计划

首先会通过子查询确认是哪个分区,然后在满足其他条件的情况下,从排序结果中获取第一条记录的id(正确),然后执行外查询,也是确认分区,从id开始的位置查询满足条件的记录,每次取一万条数据,但是当跨分区查询时,id变的无序的时候,那么就可能每次取到的数据是重复的。

六、关于limit进一步优化

可以将每次分页的下一个id传进来,这样就不用子查询了

注:

当然也可以,在外层再加order by id,但是会有什么问题呢?

子查询对所有分区进行了一次排序,外查询又进行了一次排序,性能会受影响。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值