0

这么分页,小心有坑 - jtea

 1 year ago
source link: https://www.cnblogs.com/jtea/p/17616440.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

这么分页,小心有坑

生产上有个导报表功能,工作了很长一段时间一直都很稳,没出现过什么问题,最近运营同学突然反馈导出来的数据和实际的对不上,经过排查发现导出的数据有重复,也有的没导出来。
由于我们提前生成好数据(每天会truncate重新生成),所以导出的逻辑非常简单,不需要关联很多表捞数据,只需要从一张表查即可,这个表的数据量不大,发生问题时7800条左右,查询的sql也非常简单,可以选择条件导出知道时间段的数据,如下:

SELECT * FROM t_report WHERE repayment_time > 1622390400000 AND repayment_time <= 1624032000000 limit 1000,1000

为了防止一次性导出太多数据,所以我们做了分页,每次查1000条,并且repayment_time字段上建了索引。
经过一顿排查后,发现程序没啥问题,但数据也确实是重复了,也漏了一些...

我们对比了实际数据,确实有问题,有的数据出现两次,有的没出现。如下:

2856176-20230809112926834-475575863.png

有一条数据在第6页查出,在第8页又被查出。我们再次确认了程序,还是没问题,用arthas观察了分页的入参也都是正确的。
那么直接拿sql出来到数据库查询呢?发现真有问题...,确实在第6页和第8页出现。那么可以确定就是sql有问题了,这么简单的sql查数据居然重复了...
仔细看数据可以发现,在第6页的时候,查出来的数据是按照repayment_time字段排序,而第8页则是按照id进行排序,排序方式不同分页查出来的数据肯定会不同。

问题已经定位到,就是在分页过程中,mysql偷偷改了排序的规则。我们可以看不同页的执行计划如下,由于每天生成的数据不同,这里我改了页数。

2856176-20230809112941545-1198825186.png
2856176-20230809112955556-380561150.png

那么同样的sql为什么会使用不同的排序,生成不同的执行计划呢?
我们知道mysql在使用索引的时候,是会根据数据的分布进行的,也就是mysql会考虑索引的效果,如果效果好才使用。我们常说性别字段不适合建索引,就是因为这种字段的区分度很低,mysql宁愿全表扫描也不会使用这种索引。使用show index from table可以查看索引信息,cardinality就是字段的区分度,通常该值越大越适合建索引。

2856176-20230809113010535-304869683.png

我们说到mysql会考虑到数据的分布,虽然我们上面的repayment_time字段是建索引,运营当时的查询条件刚好覆盖了整个表的数据,当翻到第8页时,整个表总共也就8页,就是要查全部数据了,mysql此时会放弃使用索引,进行全表扫描。
前面的页使用到索引,默认会根据索引字段进行排序,而后面页没使用索引,默认就是根据id进行排序。
不一定是最后一页才生成不同的执行计划,比如我们的表有10w条数据,可能查到95000条数据的时候,排序就变了,也就是说mysql认为几乎要查全部数据,就会改变执行计划。

解决方式也很简单,我们可以显示指定排序方式,这样每次生成的执行计划都是一样的,如上的sql,改为:

SELECT * FROM t_report WHERE repayment_time > 1622390400000 AND repayment_time <= 1624032000000 order by repayment_time,id limit 1000,1000

注意加了order by需要考虑file sort问题,排序字段没有使用索引可能会导致性能问题。
分页写法开发有时候很容易漏掉order by,这种最好在需求阶段就和产品确认清楚,这样可以在开发和测试阶段就通过数据验证需求,当然开发也要考虑数据分布,比如数据量,有多少重复数据等,以后记得这个坑,避免再次掉入哦。

更多分享,欢迎关注我的github:https://github.com/jmilktea/jtea


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK