mysql在使用order by +limit分页时遇到的数据重复及丢失问题

最近自己在做项目组遇到了order by+limit分页时遇到的数据重复的问题,自己在网上查了一些资料:

问题现象:order by+limit会出现数据丢失及分页重复问题

直接上图:

图0(表结构):

 图1:

图2:

图2

从图中可以看出,id=30的数据在第1页,和第3,最后1页重复了2次

通过查看执行计划,发现排序方式是文件排序,

图3:

问题原因

在MySQL 5.6的版本上,优化器在遇到order by limit语句的时候,做了一个优化,即使用了priority queue。

如果order by的列有大量重复的值的时候, mysql会随机选取这些行,具体根据执行计划有所不同。

很显然,这是由于order by 排序导致的,那么mysql order by排序底层是采用什么机制呢?

分析原因:

order by排序

MySQL支持二种方式的排序,IndexFileSort

  • Index排序:索引排序,即我们通常为了查询建立的索引,也就是说,Mysql能为排序和查询使用相同的索引;
  • FileSort:文件排序,当不能使用Index排序时,使用文件排序。FileSort排序有两种方法,堆排序和快速排序。

Index排序使用场景

  • where + order by 场景
    如果order by的字段加了索引,则使用索引排序;
  • 只有order by场景
    order by中字段满足组合索引字段排序,则使用组合使用,
  • order by id(主键)
    不管有没有where条件,或者where 字段有没有索引,只要order by id,都会使用Index 排序,而不会使用FileSort

其余情况下,使用FileSort排序,可以通过EXPLAIN中的Extra来判断使用哪种排序方式。

从图3也可以看出直接使用Order by limit确实会使用FileSort。

FileSort排序使用场景

在不能使用Index索引排序的时候,使用FileSort排序,FileSort排序有两种方式:

  • 堆排序:在排序量不大的情况下,使用堆排序

  • 快速排序:在需要大量排序的情况下,使用快排

从执行计划中只能看出来是使用Index排序,还是FileSort排序。而使用堆排序还是快排,是Mysql根据待排序数据量的大小进行切换具体根据函数check_if_pq_applicable进行判定的。

有一个简单的判断,如果使用的是order by limit n ,且在数据量不大的情况下(数据可以在内存中加载),使用的是堆排序;
而当n到了一个数量级的时候会切换成快排,具体使用那种算法是优化器通过函数check_if_pq_applicable进行判定的。
同时,如果没有limit时,就算数据量小,使用的也是快排(这个结论是从下面的参考文章中得出的)。

在大量排序的情况下快速排序是有优势的,而堆排序使用优先队列只完成少量的排序是有优势的,因为它根本不需要排序完成只排序你需要的数据量就可以了,MySQL认为快速排序的速度是堆排序的3倍。

问题释疑

从导致问题的sql,我们可以判断使用的是FileSort排序中的堆排序。那么为啥堆排序会出问题呢?

因为快速排序和堆排序是不稳定的排序算法,也就是对于重复值是不能保证顺序的。而直接利用索引的话其返回数据是稳定的,因为索引的B+树叶子结点的顺序(就是聚簇索引的顺序)是唯一且一定的。
因此在这种不稳定的算法情况下上面的查询出现了不一样的结果,归根结底就是使用索引避免排序和堆排序对于重复值的处理上是不同的。

order by limit排序时,根据取值大小创建相应容量的堆,即
order by limit 0,10;——创建一个10容量的堆,然后取前10
order by limit 10,10 ——创建一个20容量的堆,然后取10到20

那现在开始给org_num加索引,使用where条件

图4

图5:

图6:

发现分页的数据没有重复的了,执行计划走的也是索引排序。

虽然加了索引,但是如果不使用where 条件,那是走索引排序还是文件排序呢?下图就是见证奇迹的时刻:

图7:

图8:

所以即使order by的字段带有索引,但是不加where条件,依旧是文件排序。

最后总结如下:

1.不加where条件,order by +不带索引的字段或带索引的字段+limit 都会走file sort,在order by 的字段值大量重复的情况下会出现分页的数据重复及丢失的问题。

2.加了where 条件(无论where 条件的字段是否有索引),但只要order by 带索引的字段+limit ,那么就会走索引排序,在order by 的字段值大量重复的情况下应该不会出现数据重复。

解决方案:

再加一个排序字段(通常是主键id),使其排序唯一,就可以解决问题了。

自我能力有限,如有不对的地方,欢迎大家指正、交流哦!

MySQLORDER BY语句用于对查询结果进行排序。当我们在分页查询中使用ORDER BY语句,可能会影响查询性能,原因如下: 1. 排序操作需要消耗额外的计算资源。ORDER BY语句会对查询结果进行排序,这需要对每条记录进行比较和排序操作,消耗CPU和内存资源。 2. 排序操作可能需要使用表。如果查询结果集很大,MySQL可能会使用表来存储中间结果,然后在临表上进行排序操作。这会增加磁盘IO操作和存储开销。 3. 排序字段的索引可能无效。如果排序的字段没有相应的索引,MySQL将无法高效地进行排序操作,而是会执行全表扫描来排序结果。全表扫描会导致查询性能下降。 为了优化分页查询中的排序操作,可以考虑以下方法: 1. 使用索引覆盖查询。如果排序字段有相应的索引,并且查询的字段只包含索引字段,MySQL可以直接使用索引进行排序,避免全表扫描和临表的使用。 2. 限制查询结果集大小。如果只需要获取前几条记录,可以使用LIMIT子句限制结果集的大小,减少排序操作的开销。 3. 避免在分页查询中频繁改变排序字段。如果每次分页查询都使用不同的排序字段,MySQL无法充分利用缓存和索引,导致性能下降。最好在分页查询中保持相同的排序字段。 总而言之,分页查询中使用ORDER BY语句可能会影响性能,但可以通过使用索引、限制结果集大小和保持相同的排序字段等方法进行优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值