Mysql學習之order by的工作原理

2021-10-04 06:52:15 字數 3524 閱讀 4034

在你開發應用的時候,一定會經常碰到需要根據指定的字段排序來顯示結果的需求。假設你要查詢城市是「杭州」的所有人名字,並且按照姓名排序返回前 1000 個人的姓名、年齡。

查詢語句為:

select city,name,age from t where city='杭州' order by name limit 1000 ;
全欄位排序為避免全表掃瞄,我們需要在 city 字段加上索引。

通常情況下,這個語句執行流程如下所示 :

初始化 sort_buffer,確定放入 name、city、age 這三個字段;

從索引 city 找到第乙個滿足 city='杭州』條件的主鍵 id,也就是圖中的 id_x;

到主鍵 id 索引取出整行,取 name、city、age 三個欄位的值,存入 sort_buffer 中;

從索引 city 取下乙個記錄的主鍵 id;

重複步驟 3、4 直到 city 的值不滿足查詢條件為止,對應的主鍵 id 也就是圖中的 id_y;

對 sort_buffer 中的資料按照字段 name 做快速排序;

按照排序結果取前 1000 行返回給客戶端。

我們暫且把這個排序過程,稱為全欄位排序,執行流程的示意圖如下所示

圖中「按 name 排序」這個動作,可能在記憶體中完成,也可能需要使用外部排序,這取決於排序所需的記憶體和引數 sort_buffer_size。

sort_buffer_size,就是 mysql 為排序開闢的記憶體(sort_buffer)的大小。如果要排序的資料量小於 sort_buffer_size,排序就在記憶體中完成。但如果排序資料量太大,記憶體放不下,則不得不利用磁碟臨時檔案輔助排序。

這個演算法有乙個問題,就是如果查詢要返回的字段很多的話,那麼 sort_buffer 裡面要放的字段數太多,這樣記憶體裡能夠同時放下的行數很少,要分成很多個臨時檔案,排序的效能會很差。

rowid 排序

如果 mysql 認為排序的單行長度太大會怎麼做呢?

接下來,我來修改乙個引數,讓 mysql 採用另外一種演算法。

set max_length_for_sort_data = 16;
max_length_for_sort_data,是 mysql 中專門控制用於排序的行資料的長度的乙個引數。它的意思是,如果單行的長度超過這個值,mysql 就認為單行太大,要換乙個演算法。

新的演算法放入 sort_buffer 的字段,只有要排序的列(即 name 字段)和主鍵 id。

但這時,排序的結果就因為少了 city 和 age 欄位的值,不能直接返回了,整個執行流程就變成如下所示的樣子:

初始化 sort_buffer,確定放入兩個字段,即 name 和 id;

從索引 city 找到第乙個滿足 city='杭州』條件的主鍵 id,也就是圖中的 id_x;

到主鍵 id 索引取出整行,取 name、id 這兩個字段,存入 sort_buffer 中;

從索引 city 取下乙個記錄的主鍵 id;

重複步驟 3、4 直到不滿足 city='杭州』條件為止,也就是圖中的 id_y;

對 sort_buffer 中的資料按照字段 name 進行排序;

遍歷排序結果,取前 1000 行,並按照 id 的值回到原表中取出 city、name 和 age 三個字段返回給客戶端。

這個執行流程的示意圖如下:

對比圖 1 的全欄位排序流程圖你會發現,rowid 排序多訪問了一次表 t 的主鍵索引,就是步驟 7。

需要說明的是,最後的「結果集」是乙個邏輯概念,實際上 mysql 服務端從排序後的 sort_buffer 中依次取出 id,然後到原表查到 city、name 和 age 這三個欄位的結果,不需要在服務端再耗費記憶體儲存結果,是直接返回給客戶端的。

全欄位排序 vs rowid 排序

如果 mysql 認為記憶體足夠大,會優先選擇全欄位排序,把需要的字段都放到 sort_buffer 中,這樣排序後就會直接從記憶體裡面返回查詢結果了,不用再回到原表去取資料。

對於 innodb 表來說,rowid 排序會要求回表多造成磁碟讀,因此不會被優先選擇。

聯合索引

如果能夠保證從 city 這個索引上取出來的行,天然就是按照 name 遞增排序的話,是不是就可以不用再排序了呢?

所以,我們可以在這個市民表上建立乙個 city 和 name 的聯合索引

在這個索引裡面,我們依然可以用樹搜尋的方式定位到第乙個滿足 city='杭州』的記錄,並且額外確保了,接下來按順序取「下一條記錄」的遍歷過程中,只要 city 的值是杭州,name 的值就一定是有序的。

這樣整個查詢過程的流程就變成了:

從索引 (city,name) 找到第乙個滿足 city='杭州』條件的主鍵 id;

到主鍵 id 索引取出整行,取 name、city、age 三個欄位的值,作為結果集的一部分直接返回;

從索引 (city,name) 取下乙個記錄主鍵 id;

重複步驟 2、3,直到查到第 1000 條記錄,或者是不滿足 city='杭州』條件時迴圈結束。

可以看到,這個查詢過程不需要臨時表,也不需要排序。接下來,我們用 explain 的結果來印證一下。

從圖中可以看到,extra 欄位中沒有 using filesort 了,也就是不需要排序了。而且由於 (city,name) 這個聯合索引本身有序,所以這個查詢也不用把 4000 行全都讀一遍,只要找到滿足條件的前 1000 條記錄就可以退出了。也就是說,在我們這個例子裡,只需要掃瞄 1000 次。

覆蓋索引是指,索引上的資訊足夠滿足查詢請求,不需要再回到主鍵索引上去取資料。

按照覆蓋索引的概念,我們可以再優化一下這個查詢語句的執行流程。

針對這個查詢,我們可以建立乙個 city、name 和 age 的聯合索引

這樣整個查詢語句的執行流程就變成了:

從索引 (city,name,age) 找到第乙個滿足 city='杭州』條件的記錄,取出其中的 city、name 和 age 這三個欄位的值,作為結果集的一部分直接返回;

從索引 (city,name,age) 取下乙個記錄,同樣取出這三個欄位的值,作為結果集的一部分直接返回;

重複執行步驟 2,直到查到第 1000 條記錄,或者是不滿足 city='杭州』條件時迴圈結束。

當然,這裡並不是說要為了每個查詢能用上覆蓋索引,就要把語句中涉及的字段都建上聯合索引,畢竟索引還是有維護代價的。這是乙個需要權衡的決定。

MySQL查詢優化之ORDER BY

select id from uc user baseinfo where area code 020 limit 0,10 執行成功,當前返回 10 行,耗時 109ms.select id from uc user baseinfo where area code 020 limit 10,10...

MySQL 效能優化之 order by 一

前言 工作過程中,各種業務需求在訪問資料庫的時候要求有order by排序。有時候不必要的或者不合理的排序操作很可能導致資料庫系統崩潰。如何處理好order by排序呢?本文從原理以及優化層面介紹 order by 一 mysql中order by的原理 1 利用索引的有序性獲取有序資料 當查詢語句...

MySQL之優化order by和group by

目錄 orderby 優化order by的前提 orderby子句盡量不要產生filesort 如果orderby子句不可避免產生了filesort的優化 總結 groupby 首先我們要做的肯定是把orderby的字段建立成索引 order by盡量使用index方式而不要使用filesort方...