Mysql學習之order by的工做原理

在你開發應用的時候,必定會常常碰到須要根據指定的字段排序來顯示結果的需求。假設你要查詢城市是「杭州」的全部人名字,而且按照姓名排序返回前 1000 我的的姓名、年齡。算法

查詢語句爲:性能

select city,name,age from t where city='杭州' order by name limit 1000 ;

全字段排序優化

爲避免全表掃描,咱們須要在 city 字段加上索引。spa

一般狀況下,這個語句執行流程以下所示 :3d

  1. 初始化 sort_buffer,肯定放入 name、city、age 這三個字段;code

  2. 從索引 city 找到第一個知足 city='杭州’條件的主鍵 id,也就是圖中的 ID_X;blog

  3. 到主鍵 id 索引取出整行,取 name、city、age 三個字段的值,存入 sort_buffer 中;排序

  4. 從索引 city 取下一個記錄的主鍵 id;索引

  5. 重複步驟 三、4 直到 city 的值不知足查詢條件爲止,對應的主鍵 id 也就是圖中的 ID_Y;內存

  6. 對 sort_buffer 中的數據按照字段 name 作快速排序;

  7. 按照排序結果取前 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 字段的值,不能直接返回了,整個執行流程就變成以下所示的樣子:

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

  2. 從索引 city 找到第一個知足 city='杭州’條件的主鍵 id,也就是圖中的 ID_X;

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

  4. 從索引 city 取下一個記錄的主鍵 id;

  5. 重複步驟 三、4 直到不知足 city='杭州’條件爲止,也就是圖中的 ID_Y;

  6. 對 sort_buffer 中的數據按照字段 name 進行排序;

  7. 遍歷排序結果,取前 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 的值就必定是有序的。

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

  1. 從索引 (city,name) 找到第一個知足 city='杭州’條件的主鍵 id;

  2. 到主鍵 id 索引取出整行,取 name、city、age 三個字段的值,做爲結果集的一部分直接返回;

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

  4. 重複步驟 二、3,直到查到第 1000 條記錄,或者是不知足 city='杭州’條件時循環結束。

 

能夠看到,這個查詢過程不須要臨時表,也不須要排序。接下來,咱們用 explain 的結果來印證一下。

從圖中能夠看到,Extra 字段中沒有 Using filesort 了,也就是不須要排序了。並且因爲 (city,name) 這個聯合索引自己有序,因此這個查詢也不用把 4000 行全都讀一遍,只要找到知足條件的前 1000 條記錄就能夠退出了。也就是說,在咱們這個例子裏,只須要掃描 1000 次。

覆蓋索引是指,索引上的信息足夠知足查詢請求,不須要再回到主鍵索引上去取數據。

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

針對這個查詢,咱們能夠建立一個 city、name 和 age 的聯合索引

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

  1. 從索引 (city,name,age) 找到第一個知足 city='杭州’條件的記錄,取出其中的 city、name 和 age 這三個字段的值,做爲結果集的一部分直接返回;

  2. 從索引 (city,name,age) 取下一個記錄,一樣取出這三個字段的值,做爲結果集的一部分直接返回;

  3. 重複執行步驟 2,直到查到第 1000 條記錄,或者是不知足 city='杭州’條件時循環結束。

 

固然,這裏並非說要爲了每一個查詢能用上覆蓋索引,就要把語句中涉及的字段都建上聯合索引,畢竟索引仍是有維護代價的。這是一個須要權衡的決定。

相關文章
相關標籤/搜索