深入浅出 MySQL 优先队列
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《深入浅出 MySQL 优先队列》,聊聊MySQL、队列、优先,我们一起来看看吧!
本文转载自微信公众号「Java课代表」,作者Java课代表 。转载本文请联系Java课代表公众号。
本文适用于 MySQL 5.6 及以上版本
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=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=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. 最优解
MySQL 文档 8.2.1.19 LIMIT Query Optimization 中对此场景有如下描述:
- 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.
并在 WL#1393: Optimizing filesort with small limit 中阐述了该优化逻辑:
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:
SET optimizer_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 值的大小排序
取前五条数据构成一个小顶堆:
取下一行数据(6,2),发现 2 小于当前堆中最大的category 3,于是把(2,3)从堆中删掉,把(6,2) 入堆:
重复步骤 2,直至符合查询条件的数据都经历过比较入堆,最终堆中数据如图:
最后我们将其出堆即可得到结果,每次出堆最小元素后将最后一个元素放入堆顶,按照小顶堆重新堆化,过程如图:
可以看到,这个结果和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 文档,相关参考资料罗列在文末,如果有时间能顺着文章思路亲自读一遍参考文档,相信会有更深的收获。
6.参考资料:
《数据结构与算法之美》---王争 第 28,29 讲
《MySQL实战45讲》---林晓斌 第 04、05、10、16、17 讲
8.2.1.16 LIMIT Query Optimization---https://dev.mysql.com/doc/refman/5.6/en/limit-optimization.html
MySQL · 答疑解惑 · MySQL Sort 分页---http://mysql.taobao.org/monthly/2015/06/04/
filesort.cc---https://dev.mysql.com/doc/dev/mysql-server/8.0.18/filesort_8cc.html
WL#1393: Optimizing filesort with small limit---https://dev.mysql.com/worklog/task/?id=1393
到这里,我们也就讲完了《深入浅出 MySQL 优先队列》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于mysql的知识点!

- 上一篇
- MySQL 中的 insERT 是怎么加锁的?

- 下一篇
- 入职第一天,MySQL就崩了...
-
- 清秀的仙人掌
- 写的不错,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢作者大大分享技术贴!
- 2023-04-08 19:12:40
-
- 标致的镜子
- 这篇技术文章真及时,up主加油!
- 2023-03-10 23:53:05
-
- 畅快的豆芽
- 这篇技术文章真是及时雨啊,太细致了,很好,已收藏,关注老哥了!希望老哥能多写数据库相关的文章。
- 2023-02-26 20:33:41
-
- 疯狂的枕头
- 太全面了,已加入收藏夹了,感谢师傅的这篇博文,我会继续支持!
- 2023-02-26 05:46:15
-
- 数据库 · MySQL | 1天前 |
- MySQL设置中文界面,超简单教程来了!
- 332浏览 收藏
-
- 数据库 · MySQL | 1天前 | mysql 索引提示
- MySQL进阶必看!FORCE/USE/IGNOREINDEX用法大揭秘
- 182浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- 手把手教你写MySQL存储过程,小白也能轻松上手
- 163浏览 收藏
-
- 数据库 · MySQL | 1天前 | mysql group by
- MySQL分组查询优化:GROUPBY原理+索引优化超全解析
- 324浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL设置中文语言,轻松拥有中文界面
- 211浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL建库语句从入门到精通:创建数据库+设置字符集&排序规则(附实例)
- 176浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- 从零开始学MySQL数据库操作,小白轻松变大神!
- 496浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL插入日期到时间字段,轻松搞定日期格式
- 484浏览 收藏
-
- 数据库 · MySQL | 1天前 | mysql 数据压缩
- MySQL怎么实现高效压缩存储?表压缩+列式存储详细解读
- 272浏览 收藏
-
- 数据库 · MySQL | 1天前 | mysql JOIN优化
- MySQL优化JOIN操作:七大技巧教你提升关联查询速度
- 106浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL出现中文乱码?超详细解决方案一次性搞定
- 211浏览 收藏
-
- 数据库 · MySQL | 1天前 |
- MySQL主从复制这样配!搞懂这些参数,replication稳了~
- 131浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 茅茅虫AIGC检测
- 茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
- 18次使用
-
- 赛林匹克平台(Challympics)
- 探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
- 50次使用
-
- 笔格AIPPT
- SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
- 57次使用
-
- 稿定PPT
- 告别PPT制作难题!稿定PPT提供海量模板、AI智能生成、在线协作,助您轻松制作专业演示文稿。职场办公、教育学习、企业服务全覆盖,降本增效,释放创意!
- 53次使用
-
- Suno苏诺中文版
- 探索Suno苏诺中文版,一款颠覆传统音乐创作的AI平台。无需专业技能,轻松创作个性化音乐。智能词曲生成、风格迁移、海量音效,释放您的音乐灵感!
- 57次使用
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- 搞一个自娱自乐的博客(二) 架构搭建
- 2023-02-16 244浏览
-
- B-Tree、B+Tree以及B-link Tree
- 2023-01-19 235浏览
-
- mysql面试题
- 2023-01-17 157浏览
-
- MySQL数据表简单查询
- 2023-01-10 101浏览