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.pswp.cn/news/530587.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/530587.shtml
英文地址,請注明出處:http://en.pswp.cn/news/530587.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

int signed in mysql_【轉】mysql 中int類型字段unsigned和signed的區別

轉自https://www.cnblogs.com/wangzhongqiu/p/6424827.html用法:mysql> CREATE TABLE t ( a INT UNSIGNED, b INT UNSIGNED )探索一:正負數問題拿tinyint字段來舉例,unsigned后,字段的取值范圍是0-255,而signed的范…

navicat fo mysql 教程_Navicat For MySQL的簡單使用教程

1.前提是必須先安裝好MySQL數據庫(Mac下安裝MySQL數據庫見前一篇)2.安裝Navicat3.點擊navicate左上角:連接->MySQL->先測鏈接下,如果提示連接成功,就可以填寫連接名,點擊連接即可。雙擊剛創建的連接下面會有四個數據庫用naV…

mysql linux32_Linux 配置 mysql 5.7.32 實操記錄

下載環節官網自行獲取檢查環境環節1. 檢測自帶mysql#rpm -qa | grep mysql2.刪除 “1” 找到的 所有#rpm -e --nodeps 名稱3.查詢所有mysql文件夾#whereis mysql#find / -name mysql刪除所有結果 (rm -rf 文件路徑)安裝環節1. 切換路徑#cd /usr/local2.拷貝mysql安裝包到此目錄…

mysql8.0 tar安裝_CentOS7安裝MySQL8.0 tar包

CentOS7安裝MySQL8.0 tar包一、卸載1. 查看有沒有安裝mysqlrpm -qa | grep mysql刪除#rpm -e --nodeps mysql-libs-5.1.71-1.el6.i686 或# for i in $(rpm -qa|grep mysql);do rpm -e $i --nodeps;done2. 使用 find / -name mysql 命令查找原有mysql的相關配置文件,…

mysql官網 ab_MySQLAB同步

MySQL 支持單向、異步復制,復制過程中一個服務器充當主服務器,而一個或多個其它服務器充當從服務器。主服務器將更新寫入二進制日1 . 介紹MySQL 支持單向、異步復制,復制過程中一個服務器充當主服務器,而一個或多個其它服務器充當從服務器。主服務器將更新寫入二進制日志文件,并…

jtree和mysql_java 已經獲取某個mysql數據庫的所有表名 創建JTree

展開全部那只e68a843231313335323631343130323136353331333335303530能創建一層的JTree ?import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import javax.swing.JFram…

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主從表結構差異_mysqldiff對比主從表結構是否一致

mysqldiff該工具是官方mysql-utilities工具集的一個腳本,可以實現主從服務器表結構是否一致。數據校驗需要使用Percona的pt-table-checksum工具。安裝:# tar zxvf mysql-utilities-1.5.4.tar.gz# cd mysql-utilities-1.5.4# python setup.py install使用…

mysql 存儲過程插入慢_mysql存儲過程太慢怎么辦

mysql存儲過程太慢的解決方法:首先打開my.cnf配置文件;然后添加配置【long_query_time1】;接著通過【tail -f /tmp/logs/mysqld.log】命令監控sql;最后進行針對性的優化即可。解決方法:第一步:修改/etc/my.…

mongoose換成mysql_Package - tms-koa

tms-koa基于koa的輕量級快速開發框架,包含 MVC 中的 M 和 C 兩部分,適合于實現 API 服務和前后端徹底分離的應用。內置基于 access_token 的訪問鑒權機制,更容易實現 API 調用的負載分擔。內置通過連接池訪問 MySQL 數據庫,支持進…

導出遠程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 升級了最新版,我也迫不及待升級。 升級最新版軟件依賴有很多好處,總之作為一個開發者你發布新版本肯定是升級改造的工作…

php讀取mysql數據無法修改時間_php設置mysql查詢讀取數據的超時時間

php可以設置mysql查詢的超時時間估計大家不知道吧,一般都直接在mysql中進行設置了,下面我們來為各位介紹一下php設置mysql查詢讀取數據的超時時間吧。現象:php能通過代理正常連接到mysql。但是,執行query后,一直等待&a…

mysql無序id怎么優化limit_MYSQL分頁limit速度太慢優化方法

原標題:MYSQL分頁limit速度太慢優化方法在mysql中limit可以實現快速分頁,但是如果數據到了幾百萬時我們的limit必須優化才能有效的合理的實現分頁了,否則可能卡死你的服務器哦。當一個表數據有幾百萬的數據的時候成了問題!如 * fr…

反積分飽和 程序_用抗積分飽和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) …