mysql外部排序_深入浅出MySQL优先队列(你一定会踩到的order by limit 问题)

0.先抛问题

假设字段category无索引且有重复值,order by category 和 limit 组合使用的结果会和预期不符。

问题复现:

表结构(就是两个字段)

CREATE TABLE `ratings` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`category` int(11) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDBAUTO_INCREMENT=11DEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_general_ci;

对所有数据按category字段排序: select * from ratings order by category;

id

category

1

1

5

1

10

1

3

2

4

2

6

2

9

2

2

3

7

3

8

3

当我们想分页展示前5条时使用select * from ratings order by category limit 5;

期望得到的ID顺序是1 5 10 3 4。

但实际结果如下:

id

category

1

1

10

1

5

1

3

2

4

2

怎么肥似?MySQL 出 Bug 了?

可能有同学遇到过这个问题,百度或谷歌一下解决了,你有没有想过,你查到的办法是最优解吗?别人是怎么得出这个办法的?MySQL 为什么会这样做,跟版本有关吗?

先抛结论:

最优解是后面再加个列值唯一的排序字段,如:order by category,id;

MySQL 为什么这样做?答案是为了快!(MySQL 5.6及其之后才有此优化)

次优解是对order by后面的category 加索引(为什么是次优解?看完本文你将会有答案);

下面课代表将还原一下这 3 条结论的产出过程。

1. 最优解

If multiple rows have identical values in the ORDER BY columns, the server is free to return those rows in any order, and may do so differently depending on the overall execution plan. In other words, the sort order of those rows is nondeterministic with respect to the nonordered columns.

One factor that affects the execution plan is LIMIT, so an ORDER BY query with and without LIMIT may return rows in different orders.

总结来说就是:

当 ORDER BY 列的字段值存在重复,那么这条 ORDER BY 语句返回的数据顺序会因为LIMIT的存在而变得不一样

这是 MySQL 默认对该场景做的优化,如果你需要保证加不加 LIMIT 顺序都要一致,官方也给出了办法:

If it is important to ensure the same row order with and without LIMIT, include additional columns in the ORDER BY clause to make the order deterministic.

就是在ORDER BY 后面再多加一个排序字段(比如 ID 字段)。

以上描述最早出现在MySQL 5.6文档中,从这个版本开始,引入了这个针对ORDER BY LIMIT 的优化。

好了, 针对文中的场景,我们只需要select * from ratings order by category,id;即可解决。

那么问题来了,MySQL 为什么要做这么一个看似是 Bug 的优化?

2.MySQL 的 ORDER BY 逻辑

顾名思义,ORDER BY 就是排序。

执行一下explain select * from ratings order by category limit 5;

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: ratings

partitions: NULL

type: ALL

possible_keys: NULL

key: NULL

key_len: NULL

ref: NULL

rows: 10

filtered: 100.00

Extra: Using filesort

1 row in set, 1 warning (0.00 sec)

可以看到 Extra: Using filesort 表示需要排序。

正常情况下, MySQL 会有内存排序和外部排序两种:

如果待排序的数据量小于sort buffer size,排序就在内存中完成(快速排序);

如果待排序的数据量大于sort buffer size,就使用临时文件进行外部排序(归并排序);

很明显,这两种排序都是对所有结果全部排序,讲道理,不管有没有LIMIT,都是从排完序的结果中按顺序取需要的条数,有没有LIMIT是不会影响返回的结果顺序的。

但是,MySQL 5.6 版本针对 ORDER BY LIMIT做了个小优化(排序字段无索引,且列值不唯一时):优化器在遇到 ORDER BY LIMIT语句的时候,使用了priority queue。

filesort.cc 中有如下伪代码描述该优化:

while (get_next_sortkey())

{

if (using priority queue)

push sort key into queue

else

{

try to put sort key into buffer;

if (no free space in sort buffer)

{

do {

allocate new, larger buffer;

retry putting sort key into buffer;

} until (record fits or no space for new buffer)

if (no space for new buffer)

{

sort record pointers (all buffers);

dump sorted sequence to 'tempfile';

dump Merge_chunk describing sequence location into 'chunk_file';

}

}

if (key was packed)

tell sort buffer the actual number of bytes used;

}

}

if (buffer has some elements && dumped at least once)

sort-dump-dump as above;

else

don't sort, leave sort buffer to be sorted by caller.

Many web customers have to do

"SELECT ... ORDER BY non_index_column LIMIT X",

When X *  is smaller than sort_buff_size we can use

the following algoritm to speed up the sort:

- Create a queue to hold 'limit' keys.

- Scan through the table and store the first (last if DESC) keys in the queue

- Return values from queue

This is much faster than the current algoritm that works as:

该 WorkLog 中记录了优化后的效果:10 to 20 times faster than a quicksort(感兴趣的同学可以去阅读原文)。

所以,就是为了快!

MySQL 认为这种场景就是求 TOP N 的问题,使用 priority queue 就能解决。

3.priority queue(优先级队列)

priority queue 其实就是堆,Java 中有java.util.PriorityQueue类,其本质就是 堆 这种数据结构。

简单解释一下什么是堆:

堆是一个完全二叉树;

堆中每一个节点的值都必须大于等于(大顶堆)或小于等于(小顶堆)其子树中每个节点的值。

如果 MySQL 使用归并或快排,需要把所有数据都排好序,再取LIMIT 的前几条,剩余已排序的数据就白白浪费了。

而采用 priority queue 可以根据 LIMIT的条数维护一个堆,只需要把所有数据在这个堆里过一遍就能得到结果。

使用如下语句可以验证 MySQL 使用了 priority queue:

SEToptimizer_trace='enabled=on';

select * from ratings order by category limit 5;

SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G;

"filesort_priority_queue_optimization": {

"limit": 5,

"chosen": true

},

可以看到 filesort_priority_queue_optimization.chosen = true

下面用流程图还原一下 priority queue 的执行逻辑(以LIMIT 5为例):

友情提示:图中的小顶堆以 category 值的大小排序

1.  取前五条数据构成一个小顶堆:

3f7d03f08c38ab97d95fce5fbd4ccc43.png

1.  取下一行数据(6,2),发现 2 小于当前堆中最大的category 3,于是把(2,3)从堆中删掉,把(6,2) 入堆:

f37d0b8f55577ea8ef30172dd5d56dc6.png

1.  重复步骤 2,直至符合查询条件的数据都经历过比较入堆,最终堆中数据如图:

7e649a77f1ccd04837559e2faae6290d.png

以上就是通过 priority queue 找到 最小的 5 行 category 数据的执行过程。

最后我们将其出堆即可得到结果,每次出堆最小元素后将最后一个元素放入堆顶,按照小顶堆重新堆化,过程如图:

28bf51a53c958a6c8f9b1321c405eaaf.png

可以看到,这个结果和select * from ratings order by category limit 5;的输出一致

4.加索引为什么是次优解

显然,按照ORDER BY 的逻辑,直接对排序字段加索引也可以省去内存排序步骤,从而解决这个问题。

但索引也不是银弹,多出来的category索引会增加表的维护成本,如果没有明显的业务需要,单纯为了绕过这个priority queue的优化而加索引,课代表认为有点得不偿失。

尤其是当表数据量非常大的时候,索引的体量会很可观。而且,针对文中场景,category作为分类字段,重复率会比较高,即使有按分类查询的业务 SQL ,MySQL 也不一定会选取这条索引。

综上,针对本场景,个人认为order by category,id才是该问题的最优解。

PS:会不会有人问:关我鸟事,我从没写过带 LIMIT 的 SQL 啊!

难道你写的 CRUD 功能都不带分页的吗?PageHelper 源码去了解一下?

5. 总结

本文案例是课代表上线过程中遭遇到的实际问题,咨询了下周围同学,有好几个都遇到过此问题,网上文章大多浅入浅出,读完有隔靴搔痒之感,无法解答心中疑惑。遂整理此文。

其中涉及 数据结构,PageHelper,MySQL 文档,相关参考资料罗列在文末,如果有时间能顺着文章思路亲自读一遍参考文档,相信会有更深的收获。

【编辑推荐】

【责任编辑:庞桂玉 TEL:(010)68476606】

点赞 0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/530587.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

navicat fo mysql 教程_Navicat For MySQL的简单使用教程

1.前提是必须先安装好MySQL数据库(Mac下安装MySQL数据库见前一篇)2.安装Navicat3.点击navicate左上角:连接->MySQL->先测链接下,如果提示连接成功,就可以填写连接名,点击连接即可。双击刚创建的连接下面会有四个数据库用naV…

mysql官网 ab_MySQLAB同步

MySQL 支持单向、异步复制,复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器。主服务器将更新写入二进制日1 . 介绍MySQL 支持单向、异步复制,复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器。主服务器将更新写入二进制日志文件,并…

mvc json 乱码_你了解JSON吗?——Jackson、FastJson在SpringMVC中的简单使用

原文参考分享自CSDN:你了解JSON吗?--Jackson、FastJson在SpringMVC中的简单使用_欢迎来到 Baret~H 的博客-CSDN博客1. 什么是 JSONJSON(JavaScript Object Notation, JS 对象标记)是一种轻量级的数据交换格式采用完全独立于编程语…

format 函数包含_Python成为专业人士笔记-高级对象Format格式化

“专业人士笔记”系列目录:创帆云:Python成为专业人士笔记--强烈建议收藏!每日持续更新!​zhuanlan.zhihu.com在存储和转换数据输出供查看时,字符串格式可能变得非常重要。Python提供了本文概述的各种字符串格式化方法…

python 预测算法_Python 与金融数据使用机器学习算法预测交易策略

记得 关注、分享、点在看呀~ 这样您就能持续收到优质的推送啦这一期,我们将使用上一期处理好的数据特征和标签训练机器,然后预测交易策略。我们将分别使用 K近邻算法和集成学习两种方法分别完成模型的训练和预测。FinTech HistoryPython 与金…

mysql 存储过程插入慢_mysql存储过程太慢怎么办

mysql存储过程太慢的解决方法:首先打开my.cnf配置文件;然后添加配置【long_query_time1】;接着通过【tail -f /tmp/logs/mysqld.log】命令监控sql;最后进行针对性的优化即可。解决方法:第一步:修改/etc/my.…

导出远程mysql数据库中的表_shell脚本实现导出远程mysql数据库表数据至本地

bin/main.sh脚本内容 #!/bin/bash#作用:用于同步远程mysql数据库表数据至本地#作者:丁艺博source /etc/profilesource ~/.bash_profileexport LANGen_US.UTF-8export RUN_HOME$(cd "$(dirname "$0")"; echo "${PWD%/*}")s…

商业智能解决方案_格至智能开关:简单便捷的商业智能照明解决方案

美莱恩智能照明推出的格至智能调光开关,是一款便捷、可轻松实现扩展的智能照明系统。它能够节约能源,并在为各种空间工作或者学习的人们,营造最舒适的照明环境。借助美莱恩SLT单火线传输技术,在新建或者改造项目中,你将…

vue 安装 less_解决旧Vue项目升级less-loader 6.0.0报错

作为一个爱折腾的主,我的package随时都是ncu -u! 何为ncu,就是检查nodejs npm/yarn项目依赖最新版本package.json一个插件! 这不,前几天less-loader 升级了最新版,我也迫不及待升级。 升级最新版软件依赖有很多好处,总之作为一个开发者你发布新版本肯定是升级改造的工作…

反积分饱和 程序_用抗积分饱和PID控制传递函数为G(s)的被控对象

题目:用抗积分饱和PID控制传递函数为G(s)的被控对象G(s)523500/(s^387.35s^210470s)二、抗积分饱和原理积分饱和现象是在系统存在一个方向的偏差,PID控制器的输出由于积分作用的不断加大而加大,从而导致执行器达到极限位置,如果控…

mysql top 1效率_TOP 1比不加TOP慢的疑惑

问题描述: 有一个查询如下,去掉 TOP 1 的时候,很快就出来结果了,但加上 TOP 1 的时候,一般要 2~3 秒才出数据,何解? SELECT TOP 1 ??? A . INVNO FROM A , B WHERE A . Item B . ItemNumber…

jieba库词频统计_用jieba库统计文本词频及云词图的生成

一、安装jieba库:\>pip install jieba #或者 pip3 install jieba二、jieba库解析jieba库主要提供提供分词功能,可以辅助自定义分词词典。jieba库中包含的主要函数如下:jieba.cut(s) …

mysql查看表描述_MySQL表记录操作介绍(重点介绍查询操作)

MySQL表记录操作指的是对数据库表中数据进行CRUD增删改查操作,一下将一一给大家介绍,重点介绍查询操作。一、插入数据(INSERT)二、删除数据(DELETE)三、修改数据(UPDATE)四、查询数据(SELECT)下面将以例子对数据查询进行详细讲解:例子&#x…

python邮件的图片放在哪里_用python保存电子邮件中的嵌入图像

我试图在一个嵌入的电子邮件中抓取图像。问题是我保存的图像不可读&#xff0c;我不知道为什么。电子邮件(保存为我在代码开头加载的文件)&#xff1a;MIME-Version: 1.0Received: by 10.100.120.7 with HTTP; Tue, 18 Oct 2011 10:36:48 -0700 (PDT)In-Reply-To: <8B4FDE07…

支付宝支付对账单java_[Java]解析支付宝对账单csv

配置相关公钥和私钥这些需要在支付宝的账户中心配置image.png这些内容在支付宝平台上都有教程,因为下载对账单这个功能比较简单,不需要入聚石塔下载对账单https://docs.open.alipay.com/20180417160701241302/fd3qt1官方文档写的很清楚,而且能直接用,将配置好的公钥私钥APPID等…

cmd编译java文件中文乱码_乱码 HelloWorld 世界你好 cmd 执行输出的中文java 显示乱码 解决 另附 win无法执行编译运行javac java编译文件的解决方案...

【博客园cnblogs笔者m-yb原创&#xff0c;转载请加本文博客链接&#xff0c;笔者公众号aandb7 爱码一生&#xff0c;QQ群927113708, github: https://github.com/mayangbo666】这是一篇java入门:java是众多编程语言之一, 就是开发的工具技术, 没什么特别, 是应用广, 严谨的语言…

自我学习--关于如何设计光耦电路

本人在项目中多次设计光耦电路&#xff0c;目前电路在项目中运行比较平稳&#xff0c;所以总结一下自己的设计经验&#xff0c;与大家交流一下&#xff0c;如有错误还希望大家指出改正&#xff0c;谢谢&#xff08;V&#xff1a;Smt15921588263&#xff1b;愿与大家多交流&…

java websocket ie8_websocket兼容IE8

最近由于项目需要做实时聊天功能&#xff0c;选择了html5的websocket方案(事实上node.jssocket.io兼容性更好&#xff0c;个人觉得这个方案更加完美)&#xff0c;websocket实现实时聊天的demo网上很多&#xff0c;但是兼容IE8的资料却很少&#xff0c;这块折腾了很久。websoket…

python父亲节祝福_父亲节祝福语精选简短 父亲节祝福语简短独特

1.您的坚忍不拔和铮铮硬骨是我的榜样&#xff0c;我从您那儿汲取到奋发的力量&#xff0c;走过挫折&#xff0c;迈向成功&#xff0c;爸爸&#xff0c;您是我永远的榜样&#xff0c;我爱您&#xff01;祝您节日快乐&#xff01;2.您的怀抱&#xff0c;是我的小天地&#xff1b;…

java安装 hello_安装JAVA步骤,并编写HELLOWORLD程序

安装Java步骤&#xff0c;并编写helloworld程序1.安装JDK(Java Development Kit)JDK是Java开发工具包(Java Development Kit)的缩写。它是一种用于构建在J Java 平台上发布的应用程序、applet和组件的开发环境。即编写Java程序必须有JDK&#xff0c;它提供了编译Java和运行Java…