1. 程式人生 > 其它 >開啟order by的大門,一探究竟《死磕MySQL系列 十二》

開啟order by的大門,一探究竟《死磕MySQL系列 十二》

https://www.cnblogs.com/fkaka/p/15612010.html

在日常開發工作中,你一定會經常遇到要根據指定欄位進行排序的需求。

這時,你的SQL語句類似這樣。

selectid,phone,codefromevt_smswherephonelike'13020%'orderbyiddesclimit10

這個SQL的邏輯是十分清晰明瞭,但其內部的執行原理你知多少。

接下來,本期文章將帶你開啟order by的大門一探究竟。

本期所有結論都基於MySQL8.0.26版本

最新文章

字串可以這樣加索引,你知嗎?《死磕MySQL系列 七》

無法復現的“慢”SQL《死磕MySQL系列 八》

什麼?還在用delete刪除資料《死磕MySQL系列 九》

MySQL統計總數就用count(*),別花裡胡哨的《死磕MySQL系列 十》

文章總目錄

一、常見的Extra幾個資訊

在MySQL中想看一條SQL的效能不僅僅看是否用上了索引,還要看Extra中的內容,以下內容來自官方文件,給你最準確的學習資料。

using index

根據索引樹可直接檢索列資訊,無需額外的操作來讀取實際的行。

索引列即為查詢列,也為條件列。

using index condition

下面這條語句name為普通索引,age無索引。

select * from table where name = ? and age = ?

索引下推是在MySQL5.6及以後的版本出現的。

之前的查詢過程是,先根據name在儲存引擎中獲取資料,然後在根據age在server層進行過濾。

在有了索引下推之後,查詢過程是根據name、age在儲存引擎獲取資料,返回對應的資料,不再到server層進行過濾。

當你使用Explain分析SQL語句時,如果出現了using index condition那就是使用了索引下推,索引下推是在組合索引的情況出現機率最大的。

using index for group_by

只查索引列,對索引列使用了group by

explainselectphonefromevt_smswherephone="13054125874"groupbyphone;

using where

查詢的列被索引覆蓋,並且where篩選條件是索引列之一,但不是索引的前導列,Extra中為Using where; Using index, 意味著無法直接通過索引查詢來查詢到符合條件的資料

查詢的列被索引覆蓋,並且where篩選條件是索引列前導列的一個範圍,同樣意味著無法直接通過索引查詢查詢到符合條件的資料

zero limit

這個估計很少有小夥伴知道,就是你的SQL語句查詢數量為limit 0

using temporary

使用了臨時表,一般在使用group by、order by時會遇到。

這個也是本文即將要聊的話題。

using filesort

一般在使用group by、order by時會遇到,排序過程在記憶體中完成

Backward index scan

對索引列使用了降序操作

這裡只列舉了最常見的幾個資訊,MySQL官方文件中對Extra的解析大概有37個,感興趣的可以去看看,後期咔咔也會逐步完善這塊內容。

二、檔案排序

由於是在一些統計、排序的業務中會經常見到Extra中出現using filesort的資訊。

在MySQL8.0.26版本中對一個沒有索引的列進行排序在Extra中顯示using filesort。在低版本中需要你進行試驗在什麼情況下會出現。

在Extra中顯示的using filesort表示的就是排序,MySQL會給每個執行緒分配一塊記憶體用於排序,也被稱之為sort_buffer。這期文章和下期文章會牽扯到很多名詞,記得自己整理一下哈!

再看這條語句

那麼這條SQL執行的具體流程是什麼呢?

1、初始化sort_buffer,放入欄位phone、code欄位

2、在phone的索引樹找到主鍵值

3、根據主鍵值到主鍵索引樹中檢索處phone、code對應欄位的值,再儲存sort_buffer中

4、繼續從phone取下一個主鍵值

5、重複第三、第四,直到不滿足phone = 條件為止

6、在sort_buffer中的資料按照欄位phone做快排

7、按照快排的結果取出前10行返回改客戶端即可

問題:所有的排序都是在記憶體中進行的?

當然不是,任何記憶體都不是無限制的,是否在記憶體中排序取決於MySQL引數sort_buffer_sort。

在MySQL8.0.26版本中這個值大小預設為256kb。

當需要排序的資料量大於256kb的閥值時,則會利用臨時檔案進行輔助排序,也就是常說的歸併排序演算法實現。

sort_buffer_size跟需要臨時檔案的個數成正比,如果sort_buffer_size越小則臨時檔案的數量就越多。

如何檢視一個排序是否使用了臨時檔案,這個答案就交給大家來實現,版本不一致會導致很多結果都不同。

問題:你知道歸併排序是如何實現的嗎?

現在你知道了如果排序的資料大於sort_buffer_size會使用臨時檔案排序,這種排序使用的就是歸併排序的思想,接下來讓我們看看具體的流程是怎麼樣的。

1、把需要排序的資料分割,分割成每塊資料都可以存放到sort_buufer中

2、對每塊資料在sort_buufer中進行排序,排序好後,寫入某個臨時檔案

3、當所有的資料都寫入臨時檔案後,這時對於每個臨時檔案內部來說是有序的,但對於所有臨時檔案是無序的,所以還需要合併資料

4、假設現在存在tmp1和tmp2兩個臨時檔案,這時分別從tmp1、tmp2讀入部分資料到記憶體

5、假設從tmp1和tmp2中分別讀入[0-5]的資料,然後分別使用tmp1[0]、tmp2[0] 進行對比,一直到tmp1[5]、tmp2[5],這樣兩兩比較就可以把tmp1、tmp2合併為一個檔案。經過幾輪下來所有分割的資料都會合併為一個有序的大檔案

三、檔案排序很慢,還有其它辦法嗎

通過上面的案例,如果排序的資料量非常大則會超過sort_buffer_size的最大值,就只能使用檔案排序,檔案排序涉及了多次的檔案合併是非常消耗效能的。

在上文你有沒有發現一個細節,SQL中只需要排序code欄位,但把phone欄位也加到了sort_buufer中了。

這樣單行的資料大小無形中就增大了,這樣記憶體中能夠存放的行數就減少了,需要分割成多個臨時檔案,排序效能會很差,那麼有沒有其它方案可以解決這種問題呢?

答案是肯定有的,就是接下來要聊的rowid排序。

先看一個引數max_length_for_sort_data

預設max_length_for_sort_data的大小為4096位元組,假設現在要排序的資料非常多,我們可以修改這個引數讓其使用rowid的演算法。

MySQL中專門控制使用者排序的行資料長度的引數,如果單行的資料長度超過了這個值,則MySQL會自動更換為rowid演算法。

rowid排序的思想就是把不需要的資料不放到sort_buufer中,讓sort_buffer中只存放需要排序的欄位。

問題:如果你是設計者,你會存放那些欄位

假設現在存放只需要排序的欄位,排序很快完成了,拿到排序後的資料結果你應該怎麼辦呢?你已經無從下手了。

因此,你可以把主鍵ID的值也存放到sort_buufer中,當排序完成後通過ID回表即可得到排序後的資料。

執行流程

試想一下,這個執行流程其實跟檔案排序的流程大差不差。

只是存放到sort_buufer中的欄位變為需要排序的欄位加上主鍵欄位。

接著在sort_buufer中按照排序欄位進行排序

最後再遍歷排序結果,取需要的行數,並使用id進行回表一次,查出你需要的列即可。

注意點

這不是說使用了rowid的排序演算法後就不使用臨時檔案排序了,不是這樣的。

使用rowid只是存放到sort_buffer中的資料多個,若需要排序的資料很多還是需要使用臨時檔案的。

四、優化檔案排序

如果MySQL發現sort_buufer記憶體太小,會影響排序效率,才會採用rowid排序演算法,使用rowid演算法的好處就是sort_buffer中可以一次排序更多的行,缺點就是需要回表。

在MySQL中如果記憶體夠用,就多利用記憶體,儘量減少磁碟訪問。所有rowid的演算法不會被優先選擇,因為回表會造成過的磁碟讀。

不是所有的order by語句,都需要排序操作的,上面分析的兩種排序演算法的由來都是因為原來的資料都是無序的。

問題:什麼是有序的?

看過了索引那一期文章後,你現在應該知道以下兩點。

索引本身具有順序性,在進行範圍查詢時,獲取的資料已經排好了序,從而避免伺服器再次排序和建立臨時表的問題。

索引的底層實現本身具有順序性,通過磁碟預讀使得在磁碟上對資料的訪問大致呈順序的定址,也就是將隨機的I/O變為順序I/O。

問題:如何防止進行排序

現在你應該知道答案了,就是給需要排序的列建立聯合索引。

現在給phone、code建立一個聯合索引,對應的SQL語句如下

altertableevt_smsaddindexidx_phone_code(phone,code);

那麼執行同樣的語句就不會使用排序操作了,接下來看一下執行流程

執行流程

1、從索引(phone,code)找到滿足phone='123456'的記錄,取出phone、code的值,作為結果集的一部分直接返回

3、從索引(phone、code)取下一個記錄,同樣取出phone、code的值,作為結果集的一部分直接返回

4、重複步驟2直到查出1000行資料,或者不滿足查詢條件為止

五、總結

order by沒有用到索引時,執行計劃中會出現using filesort

using filesort根據引數sort_buffer_size的值來決定使用需要使用臨時檔案

max_length_for_sort_data引數決定是否使用rowid演算法,若放入sort_buffer的每行資料大於設定的值就會使用rowid演算法

現在你應該知道了rowid排序只是把需要排序的欄位和主鍵ID放入sort_buffer中,而檔案排序則是把查詢的所有欄位全部放入sort_buffer中。

還有rowid會多造成一次回表操作,這個你也要知道。

最後提到了優化order by語句,這裡提到了建立覆蓋索引,利用索引的有序性直接返回結果不用進行排序。

這裡並不是提倡大家在實際生產環境中盲目建立,而是根據具體業務情況,如果資料非常的小在記憶體排序是非常快的。並且覆蓋索引會佔用更多的儲存空間和維護開銷。

堅持學習、堅持寫作、堅持分享是咔咔從業以來所秉持的信念。願文章在偌大的網際網路上能給你帶來一點幫助,我是咔咔,下期見。  

轉https://www.cnblogs.com/fkaka/p/15612010.html