mysql關(guān)于排序底層原理解析
前言
本章詳細(xì)講下排序,排序在我們業(yè)務(wù)開發(fā)非常常見,有對時間進(jìn)行排序,又對城市進(jìn)行排序的。
不合適的排序,將對系統(tǒng)是災(zāi)難性的,這個不是危言聳聽。
可能有些人會想,對于排序mysql 是怎么實現(xiàn)的,它的底層原理是怎么樣的,如果我加上分頁,排序是不是就會快一些。
關(guān)于這些問題,本章詳細(xì)講解。
有人經(jīng)常問我,mysql 優(yōu)化的規(guī)則,總是不假思索的說ESR,E 是 equal ,S是sort 。
可見排序有多么重要,為了講解方便,我先畫個思維導(dǎo)圖。

上圖標(biāo)的1,2 是mysql 配置文件可以配置的。
可以通過 show variables like 'max_length_for_sort_data'; 可以具體的配置。
從圖上我們可以看到mysql 排序分為全字段排序,和 rowid 。
這是兩大類,里面又分為內(nèi)存排序,文件排序,我將從這2大類4小類講解。
全字段排序

由上圖可以看出 Extra = Using filesort 就表示了排序,但此時還不能判斷是文件排序還是內(nèi)存排序
可以根據(jù)下面介紹的方法,來確定一個排序語句是否使用了臨時文件
/* 打開optimizer_trace,只對本線程有效 */ SET optimizer_trace='enabled=on'; ? /* @a保存Innodb_rows_read的初始值 */ select VARIABLE_VALUE into @a from performance_schema.session_status where variable_name = 'Innodb_rows_read'; ? /* 執(zhí)行語句 */ select city, name,age from t where city='杭州' order by name limit 1000; ? /* 查看 OPTIMIZER_TRACE 輸出 */ SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G ? /* @b保存Innodb_rows_read的當(dāng)前值 */ select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = 'Innodb_rows_read'; ? /* 計算Innodb_rows_read差值 */ select @b-@a;

Number_of_tmp_files>0 就表示文件排序,沒有就表示是內(nèi)存排序。
sort_buffer_size 越小,那么 Number_of_tmp_files 就會越大,文件排序用的是歸并排序,也就是把數(shù)據(jù)分給多個文件,每個文件排序后,最終合并一個文件。
上面sort_mode 可以看到,這是一個全字段排序,什么是全字段排序,就拿上面這個sql 語句來說,city ,name,age 都在文件里,對name 進(jìn)行排序
這個排序的內(nèi)部是這么實現(xiàn)的:
- 初始化 sort_buffer,確定放入 name、city、age 這三個字段;
- 從索引 city 找到第一個滿足 city='杭州’ 條件的主鍵 id
- 到主鍵 id 索引取出整行,取 name、city、age 三個字段的值,存入 sort_buffer 中;
- 從索引 city 取下一個滿足 city='杭州’ 的主鍵 id;
- 重復(fù)步驟 3、4 直到 city 的值不滿足查詢條件為止
- 對 sort_buffer 中的數(shù)據(jù)按照字段 name 做快速排序;
- 按照排序結(jié)果取前 1000 行返回給客戶端。
由此我們發(fā)現(xiàn),排序會對表的所有的記錄進(jìn)行排序,然后在取出1000條
rowid
如果 排序數(shù)據(jù)的長度超過了 max_length_for_sort_data 就是 rowid排序。
排序數(shù)據(jù)的長度就是指拿上面這個例子說 name、city、age 這三個字段大于 max_length_for_sort_data 就是rowid 排序。
為什么會這樣的呢,mysql 會盡量用內(nèi)存排序,字段越長,占用空間越大,未了提高排序效率,就會用rowid 排序。
rowid排序的步驟是這樣的:
- 初始化 sort_buffer,確定放入兩個字段,即 name 和 id;
- 從索引 city 找到第一個滿足 city='杭州’條件的主鍵 id
- 到主鍵 id 索引取出整行,取 name、id 這兩個字段,存入 sort_buffer 中;
- 從索引 city 取下一個記錄的主鍵 id;
- 重復(fù)步驟 3、4 直到不滿足 city='杭州’條件為止,
- 對 sort_buffer 中的數(shù)據(jù)按照字段 name 進(jìn)行排序;
- 遍歷排序結(jié)果,取前 1000 行,并按照 id 的值回到原表中取出 city、name 和 age 三個字段返回給客戶端。
我們可以看到 rowid 會多訪問一次表,在mysql 看來,排序的復(fù)雜度高于回表的復(fù)雜度,這也是一種取舍。
綜上可以看出不管是內(nèi)存排序還是文件排序,都是很繁瑣的,那么有沒有對于這個問題有沒有優(yōu)化點了,在前面我們已經(jīng)講過了,索引一定是有序的,如果我們對city,name 建一個聯(lián)合索引,就不用mysql 重新排序,因為索引本身就是有序的。
就是如下所示:
alter table t add index city_user(city, name);
但是上面雖然不用mysql 用文件排序,但是還是要回表的,那還有沒有進(jìn)一步的優(yōu)化呢,我們可以考慮用覆蓋索引
如下所示:
alter table t add index city_user_age(city, name, age);
這樣就不用回表了,用explain 來看 Extra using index
大家要綜合考慮吧,索引越多,索引越大,會影響插入的速度的。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
MySQL中深分頁LIMIT 100000的優(yōu)化方案
在實際項目中,分頁查詢是最常見的 SQL 場景之一,但隨著業(yè)務(wù)數(shù)據(jù)量不斷增長,我們經(jīng)常會遇到深分頁的請求,本文將帶大家理解 MySQL 深分頁的本質(zhì)以及掌握高性能替代方案,感興趣的可以了解下2025-11-11
win10下安裝mysql8.0.23 及 “服務(wù)沒有響應(yīng)控制功能”問題解決辦法
這篇文章主要介紹了win10下安裝mysql8.0.23 及 “服務(wù)沒有響應(yīng)控制功能”問題解決辦法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
MySQL查看每個分區(qū)的數(shù)據(jù)量的查詢方法
在MySQL中,分區(qū)表是一種將數(shù)據(jù)分割成更小、更易管理的部分的方法,通過分區(qū),可以顯著提高查詢性能和數(shù)據(jù)管理效率,在實際應(yīng)用中,了解每個分區(qū)中的數(shù)據(jù)量有助于優(yōu)化和監(jiān)控數(shù)據(jù)庫性能,本文將介紹如何查看MySQL中每個分區(qū)的數(shù)據(jù)量,需要的朋友可以參考下2025-07-07
mysql 忘記密碼的解決方法(linux和windows小結(jié))
下面是linux和windows下mysql丟失密碼的解決辦法2008-12-12
Mysql縱表轉(zhuǎn)換為橫表的方法及優(yōu)化教程
在應(yīng)用中為了從不同的視圖去分析數(shù)據(jù),會使用不同的方案去查詢數(shù)據(jù)庫,橫表和縱表的相互轉(zhuǎn)換就是其中一個常見的情景,這篇文章主要給大家介紹了關(guān)于Mysql縱表轉(zhuǎn)換為橫表的相關(guān)資料,需要的朋友可以參考下2021-08-08
MYSQL之Doublewrite?Buffer雙寫緩存區(qū)詳解
由于MySQL頁(16KB)與Linux文件系統(tǒng)頁(4KB)不匹配,可能導(dǎo)致部分頁寫入損壞,Redo日志無法修復(fù),Doublewrite?Buffer通過內(nèi)存+磁盤雙緩沖確保數(shù)據(jù)可靠性,與Redo日志協(xié)作實現(xiàn)故障恢復(fù),相關(guān)參數(shù)用于配置2025-08-08
一文帶你解鎖MySQL實現(xiàn)行轉(zhuǎn)列的完整方法
MySQL的行轉(zhuǎn)列,不是簡單的語法堆砌,而是對數(shù)據(jù)結(jié)構(gòu)深刻理解后的重構(gòu),這篇文章主要介紹了MySQL實現(xiàn)行轉(zhuǎn)列的完整方法,有需要的小伙伴可以了解下2026-01-01

