如何解决MySQL order by limit语句的分页数据重复问题?

Java后端技术

共 3040字,需浏览 7分钟

 ·

2021-03-31 09:30

往期热门文章:

1、往期精选优秀博文都在这里了!
2、Java中八个潜在的内存泄露风险,你知道几个?
3、为什么你996猝死,你老板007都没事?
4、美团面试题:String s = new String("111")会创建几个对象?
5、一个牛逼的 多级缓存 实现方案!

文章来源:https://www.jianshu.com/p/544c319fd838


0 问题描述

在MySQL中我们通常会采用limit来进行翻页查询,比如limit(0,10)表示列出第一页的10条数据,limit(10,10)表示列出第二页。


但是,当limit遇到order by的时候,可能会出现翻到第二页的时候,竟然又出现了第一页的记录。


具体如下:


SELECT `post_title`,`post_date` FROM post WHERE `post_status`='publish' ORDER BY view_count desc LIMIT 5,5

使用上述SQL查询的时候,很有可能出现和LIMIT 0,5相同的某条记录。而如果使用如下方式,则不会出现重复的情况:


SELECT * FROM post WHERE post_status='publish' ORDER BY view_count desc LIMIT 5,5

但是,由于post表的字段很多,仅仅希望用这两个字段,不想把post_content也查出来。


为了解决这个情况,在ORDER BY后面使用了两个排序条件来解决这个问题,如下:


SELECT `post_title`,`post_date` FROM post WHERE `post_status`='publish' ORDER BY view_count desc,ID asc LIMIT 5,5


按理来说,MySQL的排序默认情况下是以主键ID作为排序条件的


也就是说,如果在view_count相等的情况下,主键ID作为默认的排序条件,不需要我们多此一举加ID asc。


但是事实就是,MySQL再order by和limit混用的时候,出现了排序的混乱情况。


1 分析问题

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


使用 priority queue 的目的,就是在不能使用索引有序性的时候,如果要排序,并且使用了limit n,那么只需要在排序的过程中,保留n条记录即可


这样虽然不能解决所有记录都需要排序的开销,但是只需要 sort buffer 少量的内存就可以完成排序。


之所以MySQL 5.6出现了第二页数据重复的问题,是因为 priority queue 使用了堆排序的排序方法,而堆排序是一个不稳定的排序方法


也就是相同的值可能排序出来的结果和读出来的数据顺序不一致。


MySQL 5.5 没有这个优化,所以也就不会出现这个问题。


也就是说,MySQL 5.5是不存在本文提到的问题的,5.6版本之后才出现了这种情况。


再看下MySQL解释sql语言时的执行顺序:

(1)     SELECT
(2)     DISTINCT <select_list>
(3)     FROM <left_table>
(4)     <join_type> JOIN <right_table>
(5)     ON <join_condition>
(6)     WHERE <where_condition>
(7)     GROUP BY <group_by_list>
(8)     HAVING <having_condition>
(9)     ORDER BY <order_by_condition>
(10)    LIMIT <limit_number>

执行顺序依次为 form… where… select… order by… limit…



由于上述priority queue的原因,在完成select之后,所有记录是以堆排序的方法排列的


在进行order by时,仅把view_count值大的往前移动。但由于limit的因素,排序过程中只需要保留到5条记录即可


view_count并不具备索引有序性,所以当第二页数据要展示时,mysql见到哪一条就拿哪一条


因此,当排序值相同的时候,第一次排序是随意排的,第二次再执行该sql的时候,其结果应该和第一次结果一样。


2 解决方法


(1)索引排序字段


如果在字段添加上索引,就直接按照索引的有序性进行读取并分页,从而可以规避遇到的这个问题。


(2)正确理解分页

分页是建立在排序的基础上,进行了数量范围分割。排序是数据库提供的功能,而分页却是衍生的出来的应用需求。


在MySQL和Oracle的官方文档中提供了limit n和rownum < n的方法,但却没有明确的定义分页这个概念。


还有重要的一点,虽然上面的解决方法可以缓解用户的这个问题,但按照用户的理解,依然还有问题


比如,这个表插入比较频繁,用户查询的时候,在read-committed的隔离级别下,第一页和第二页仍然会有重合。


所以,分页一直都有这个问题,不同场景对数据分页都没有非常高的准确性要求。



(3)一些常见的数据库排序问题

不加order by的时候的排序问题

用户在使用Oracle或MySQL的时候,发现MySQL总是有序的,Oracle却很混乱,这个主要是因为Oracle是堆表,MySQL是索引聚簇表的原因。


所以没有order by的时候,数据库并不保证记录返回的顺序性,并且不保证每次返回都一致的。


分页问题 分页重复的问题

如前面所描述的,分页是在数据库提供的排序功能的基础上,衍生出来的应用需求,数据库并不保证分页的重复问题。


NULL值和空串问题

不同的数据库对于NULL值和空串的理解和处理是不一样的


比如Oracle NULL和NULL值是无法比较的,既不是相等也不是不相等,是未知的。


而对于空串,在插入的时候,MySQL是一个字符串长度为0的空串,而Oracle则直接进行NULL值处理。

往期热门文章:

1、历史文章分类导读列表!精选优秀博文都在这里了!》

2一个牛逼的 多级缓存 实现方案!
3、阿里一面:如何保障消息100%投递成功、消息幂等性?
4、GitHub 热榜:被网友疯狂恶搞的「蚂蚁呀嘿」项目终于开源了!
5、记住!看小电影前一定要检查一下域名是不是 HTTPS 的,不然....
6、拿到年终奖后马上辞职,厚道吗?
7、Redis 内存满了怎么办?
8、在 IDE 中玩转 GitHub
9、死磕18个Java8日期处理,工作必用!
10、把我坑惨的一个MySQL双引号!

浏览 15
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报