MySQL编程——分析MySQL中使用order by和limit每次返回结果不同的问题及解决办法

news/2024/5/19 4:06:14 标签: mysql, 数据库, 堆排序, Oder by, LIMIT

作为一名开发者,我们经常会遇到各种数据库问题。其中,MySQL排序结果不一致问题是一个比较常见的问题。当我们在使用MySQL进行排序时,有时候会发现相同的查询多次执行,但排序结果却不一致。这个问题可能会给我们的业务带来困扰和不确定性。

首先我们来还原下现象,然后我们在分析下原因,找到解决办法。

一、环境准备

  1. 创建表
CREATE TABLE `tb1` (
	`id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT,
	`a` DECIMAL ( 19, 2 ) NOT NULL,
	`acid` BIGINT ( 20 ) NOT NULL,
	`prid` BIGINT ( 20 ) NOT NULL,
	PRIMARY KEY ( `id` ),
	KEY `idx_prid` ( `prid` ),
KEY `idx_acid` ( `acid` ) 
) ENGINE = INNODB AUTO_INCREMENT = 0 DEFAULT CHARSET = utf8

注意字段a 上面是没有索引的。

  1. 初始化数据
INSERT INTO `tb1` (`id`, `a`, `acid`, `prid`) 
VALUES 
(1,2.00,3,2),(2,3.00,3,2),(3,4.00,2,3),
(4,5.00,2,3),(5,6.00,2,3),(6,8.00,2,3),
(7,10.00,2,3),(8,12.00,2,3),(9,16.00,2,3),
(10,20.00,2,3),(11,6.00,2,4),(12,8.00,2,4),
(13,10.00,2,4),(14,12.00,2,4),(15,5.00,2,2),
(16,6.00,2,2);

二 、复现现象

  1. 执行两个 根据非索引字段且有重复值的 order by 排序
mysql> select * from tb1 order by a desc limit 4;
+----+-------+------+------+
| id | a     | acid | prid |
+----+-------+------+------+
| 10 | 20.00 | 2    | 3    |
| 9  | 16.00 | 2    | 3    |
| 14 | 12.00 | 2    | 4    |
| 8  | 12.00 | 2    | 3    |
+----+-------+------+------+
4 rows in set (0.00 sec)

得到id 为10, 9, 14, 8 的结果集

mysql> select * from tb1 order by a desc limit 3;
+----+-------+------+------+
| id | a    | acid | prid |
+----+-------+------+------+
| 10 | 20.00 | 2    | 3    |
| 9  | 16.00 | 2    | 3    |
| 8  | 12.00 | 2    | 3    |
+----+-------+------+------+
3 rows in set (0.00 sec)

得到id 为10 9 8 的结果集

  1. 为a字段加上索引
mysql> alter table tb1 add key ind_tb1a(a);
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> select * from tb1 order by a desc limit 3;
+----+-------+------+------+
| id | a     | acid | prid |
+----+-------+------+------+
| 10 | 20.00 | 2    | 3    |
| 9  | 16.00 | 2    | 3    |
| 8  | 12.00 | 2    | 3    |
+----+-------+------+------+
3 rows in set (0.00 sec)

得到id 为10 9 8 的结果集

mysql> select * from tb1 order by a desc limit 4;
+----+-------+------+------+
| id | a     | acid | prid |
+----+-------+------+------+
| 10 | 20.00 | 2    | 3    |
| 9  | 16.00 | 2    | 3    |
| 14 | 12.00 | 2    | 4    |
| 8  | 12.00 | 2    | 3    |
+----+-------+------+------+
4 rows in set (0.00 sec)

得到id 为10, 9, 14, 8 的结果集

从上面的测试来看对于一个非唯一字段 无论是否含有索引,结果集都是不确定的。

三、原因分析

分析不同limit N下返回的数据,发现顺序不一致的结果集有一个共同特点——顺序不一致的这几条数据,他们用来排序的参数值rank相同;

也就是说,带limit的order by查询,只保证排序值rank不同的结果集的绝对有序,而排序值rank相同的结果不保证顺序;推测MySQL对order by limit进行了优化;limit n, m不需返回全部数据,只需返回前n项或前n + m项;

上面的推测在MySQL官方文档中找到了相关的说明

If an index is not used for ORDER BY but a LIMIT clause is also present, 
the optimizer may be able to avoid using a merge file and 
sort the rows in memory using an in-memory filesort operation. 
For details, see The In-Memory filesort Algorithm.

也就是说在ORDER BY + LIMIT的查询语句中,如果ORDER BY不能使用索引的话,优化器可能会使用in-memory sort操作;

其实排序算法中,适用于取前n的算法也只有堆排序了;

参考The In-Memory filesort Algorithm可知MySQL的filesort有3种优化算法,分别是:

  • 基本filesort

  • 改进filesort

  • In memory filesort

而上面的提到的In memory filesort,官方文档这么说明:

The sort buffer has a size of sort_buffer_size.  
If the sort elements for N rows are small enough to fit in the sort buffer (M+N rows if M was specified), 
the server can avoid using a merge file and performs an in-memory sort by treating the sort buffer as a priority queue.

这里提到了一个关键词——优先级队列;而优先级队列的原理就是二叉堆,也就是堆排序

我们看下堆排序步骤

  • (1)建堆:建堆结束后,数组中的数据已经是按照大顶堆的特性组织的;数组中的第一个元素就是堆顶;

  • (2)取出最大值(类似删除操作):将堆顶元素a[1]与最后一个元素a[n]交换,这时,最大元素就放到了下标为n的位置;

  • (3)重新堆化:交换后新的堆顶可能违反堆的性质,需要重新进行堆化;

  • (4)重复(2)(3)操作,直到最后堆中只剩下下标为1的元素,排序就完成了;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

简单说下堆排序是什么,想要深入了解的同学可以查找相关资料看下。

  • 堆排序是指利用堆这种数据结构进行排序的一种算法;

  • 排序过程中,只需要个别临时存储空间,所以堆排序是原地排序算法,空间复杂度为O(1);

  • 堆排序的过程分为建堆和排序两大步骤;建堆过程的时间复杂度为O(n),排序过程的时间复杂度为O(nlogn),所以,堆排序整体的时间复杂度为O(nlogn);

堆排序不是稳定的算法,因为在排序的过程中,每取出一次堆顶元素,都需要将堆的最后一个节点跟堆顶节点互换的操作(也就是上面提到的"堆顶的移出操作"),所以可能把值相同数据中,原本在数组序列后面的元素,通过交换到堆顶,从而改变了这些值相同的元素的原始相对顺序;因此是不稳定的;

这也就是为什么当改变SQL的limit的大小,返回的排序结果中,相同排序值rank的记录的相对顺序发生变化的根本原因。

所以会出现SQL查询语句同时包含order by和limit时,当修改limit的值,可能导致 “相同排序值的元素之间的现对顺序发生改变”

原因就是MySQL对limit的优化,导致当取到指定limit的数量的元素时,就不再继续添加参与排序的记录了,因此参与排序的元素的数量变化了;而MySQL排序使用的In memory filesort是基于优先级队列,也就是堆排序,而堆排序时不稳定的,会改变排序结果中,相同排序值rank的记录的相对顺序;

四、解决方法

我们以上面的表为例来说明解决办法。

如果业务属性确保 a 字段不能唯一,则需要针对排序结果再加上 一个唯一字段的排序 比如id

mysql> select * from tb1 order by a desc ,id desc limit 4;
+----+-------+------+------+
| id | a     | acid | prid |
+----+-------+------+------+
| 10 | 20.00 | 2    | 3    |
| 9  | 16.00 | 2    | 3    |
| 14 | 12.00 | 2    | 4    |
| 8  | 12.00 | 2    | 3    |
+----+-------+------+------+
4 rows in set (0.00 sec)
mysql> select * from tb1 order by a desc ,id desc limit 3;
+----+-------+------+------+
| id | a     | acid | prid |
+----+-------+------+------+
| 10 | 20.00 | 2    | 3    |
| 9  | 16.00 | 2    | 3    |
| 14 | 12.00 | 2    | 4    |
+----+-------+------+------+
3 rows in set (0.00 sec)

使用order by id/unique_key 排序之后,前三个结果集是一致的10,9,14 。 结果集满足我们的需求。从而解决不确定性带来的问题。

排序值带上主键id,即order by rank 改为 order by rank, id 即可;


http://www.niftyadmin.cn/n/5033203.html

相关文章

文心一言 VS 讯飞星火 VS chatgpt (94)-- 算法导论9.2 3题

三、用go语言,给出 RANDOMIZED-SELECT的一个基于循环的版本。 文心一言: 在循环中实现 RANDOMIZED-SELECT 的一个版本可以如下所示。这个版本使用 Go 语言编写,利用随机化来选择一个元素,并在循环中不断地调整选择的元素&#x…

opencv 轮廓顶点重新排序----四边形

def reorder(myPoints):# print(myPoints.shape)# 创建一个与myPoints具有相同形状和类型的数组myPointsNew np.zeros_like(myPoints)# 数组重塑为一个4行2列的数组myPoints myPoints.reshape((4,2))# 计算myPoints数组中每一行(即每个点)的坐标和add …

微软发现影响 Linux 和 macOS系统的 ncurses 库漏洞

微软在 ncurses(new curses 的缩写)编程库中发现了一组内存损坏漏洞,威胁者可利用这些漏洞在易受攻击的 Linux 和 macOS 系统上运行恶意代码。 微软威胁情报研究人员 Jonathan Bar Or、Emanuele Cozzi 和 Michael Pearse 在今天发布的一份技…

深入解读什么是期权的内在价值和时间价值?

期权品种越来越丰富,对于大家套利对冲都有很多的选择。而有些初学者对时间价值一直不理解,今天呢,就给大家讲一讲深入解读什么是期权的内在价值和时间价值?本文来自:期权酱 01在期权交易过程中,想必大家都会…

代码随想录day55|392. 判断子序列115. 不同的子序列

392. 判断子序列 class Solution:def isSubsequence(self, s: str, t: str) -> bool:if len(s) 0:return Trueif len(t) 0:return Falsedp [[False] * len(t) for _ in range(len(s))]for i in range(len(t)):if t[i] s[0]:for j in range(i,len(t)):dp[0][j] Truebrea…

【Markdown】博客常用颜色、字体

▚ 01 常用颜色 默认使用face华文新魏 size4。 以绿色为例的源代码为&#xff1a; <font colorgreen face华文新魏 size4> 绿色&#xff1a;green </font>绿色&#xff1a;green 1.1 与绿相近颜色 绿色&#xff1a;green 深绿色&#xff1a;darkGreen 森林…

Web 器学习笔记(基础)

Filter 过滤器 概念&#xff1a;表示过滤器&#xff0c;是 JavaWeb 三大组件&#xff08;Servlet、Filter、Listener&#xff09;之一 作用&#xff1a;顾名思义可以过滤资源的请求&#xff0c;并实现特殊的需求 Filter 接口及它核心的 doFilter() 方法&#xff08;执行前就是…

知识联合——函数指针数组

前言&#xff1a;小伙伴们又见面啦&#xff0c;今天我们来讲解一个将函数&#xff0c;指针&#xff0c;数组这三个C语言大将整合在一起的知识——函数指针数组。同时来告诉小伙伴们我们上一篇文章的伏笔——函数指针的具体用法。 目录 一.什么是函数指针数组 二.函数指针数组…