MySQL中order by的工作原理

2021-10-08 03:52:23 字數 2896 閱讀 3539

假設有查詢語句:select city, name, age from t where city=『杭州』 order by name limit 1000,且city上建立索引

mysql為order by排序開闢的記憶體稱為sort_buffer,如果要排序的資料量小於這塊記憶體,則在記憶體中進行排序。

在記憶體中排序過程:

①初始化sort_buffer,放入name, city, age三個字段;

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

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

④從索引city取下乙個滿足條件的主鍵id;

⑤重複③和④的操作,直到不滿足條件為止;

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

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

如果要排序的資料量大於排序記憶體,則需要使用磁碟臨時檔案進行輔助排序,也就是普通的外部排序。先把所有的資料分成若干份,每份排序後放入磁碟臨時檔案,最後再對所有的有序臨時檔案進行歸併排序,合成乙個有序的大檔案。

如果查詢要返回的字段很多,那麼sort_buffer裡面要放的字段數會比較多,這樣記憶體中能夠放下的行數就會很少,就可能需要分成很多臨時檔案來輔助排序,導致效能下降。

對於單行太長的情況,mysql將使用rowid排序。mysql中用於設定待排序的行資料的長度的引數是max_length_for_sort_data,如果大於這個值,則轉而使用rowid排序。

排序過程:

①初始化sort_buffer,只放入兩個字段,name和主鍵id;

②從索引city找到第乙個滿足city=『杭州』的主鍵id;

③從主鍵索引上取出整行,並取出name和主鍵id,放入sort_buffer;

④從索引city上取得下乙個滿足條件的主鍵id;

⑤重複③和④,直到不滿足條件為止;

⑥對sort_buffer中的資料安裝name進行排序;

⑦排序完成後的結果取前1000行,再拿著這1000行的主鍵id回到原表取得city, name, age三個字段值,依次返回給客戶端。

可以看出,這種排序方式需要做額外的回表取資料操作。

注意:mysql實在擔心排序記憶體不夠的時候,才會採用rowid排序,這樣排序過程中一次可以排序很多行,但是需要回表取資料;如果記憶體足夠,還是會選擇全欄位排序,即把所要查詢的字段全部讀入sort_buffer中,排完序直接返回。因此,rowid排序並不會被優先選擇。

合理地利用索引,將可以做到執行order by的時候,竟然可以不用排序,因為索引上的資料已經做好了排序。

聯合索引:

如果對city, name建立聯合索引,則對於所有city=『杭州』的索引,name部分是有序的。此時,排序過程變成:

①從『city, name』索引上取得乙個滿足city=『杭州』的主鍵id;

②到主鍵索引上取得整行,取city, name, age三個字段值,返回給客戶端;

③在『city, name』索引上取下乙個滿足條件的主鍵id;

④重複②和③,直到第1000條記錄,或者不滿足條件為止。

覆蓋索引:

使用『city, name』的聯合索引,每次還是需要再回表取資料,如果再激進一點,不想回表的話,就需要使用覆蓋索引,即對』city, name, age』建立聯合索引。此時就就可以在索引樹上,直接取得所有需要的資訊。需要注意的是,索引是有維護代價的,並不是每次都會為了查詢而建立聯合覆蓋索引,需要在兩者間進行權衡。

方法一:使用order by rand() limit 3

假設單詞表有兩個字段(主鍵id和word),現在我們要從單詞表中隨機地取出三個英語單詞。如果使用order by rand()這種方式,需要using temporary和using filesort。將表中每行的word字段值讀入記憶體臨時表,同時對word值呼叫rand()函式生成乙個(0, 1)的隨機小數,作為該臨時表的第二個欄位r,然後要按照欄位r進行rowid排序。之所以選擇時rowid排序,因為此時使用的是記憶體臨時表,回表過程很快,只需要直接訪問記憶體來獲取資料,現在優化器會優先考慮用於排序的行越短越好,所以選擇rowid排序。接下來,要從記憶體臨時表中取出隨機數字段和位置資訊(可以把記憶體臨時表看作是陣列,位置就是陣列的索引)放入sort_buffer中進行排序,排序完成後還要回記憶體臨時表取出所要查詢的字段,返回給客戶端。

可以看到,使用這種方式查詢的代價比較大,因此要盡量避開。

方法二:隨機取某1行,取3次

①取整個表的總行數,假設為n;

②取y1=floor(n*rand()),floor()表示取整;

③再用limit y1,1取得一行;

④重複②和③,取再取兩行y2和y3。

掃瞄的總行數=n+(y1+1)+(y2+1)+(y3+1)。執行代價小於方法一。

方法三:基於方法二的優化

①取整個表的總行數,假設為n;

②取y1=floor(nrand()),y2=floor(nrand()),y3=floor(n*rand()),floor()表示取整;

③取y1、y2、y3中最大的數為m,最小的數為n,剩下的數為t;

④執行select * from t limit n, m-n+1;

⑤取出m-n+1行,此時,取出的第一行就是n+1行,第(t-n+1)行就是第t+1行,最後乙個為m+1行。

掃瞄的總行數=n+(m+1),掃瞄行數少於方法二。

關於mysql中orderby的幾個注意事項

在mysql php查詢中 對字段的型別要求比較高 例如varchar 11 和int 這兩種型別 在許多時候可以通用但是在下面這種情況時候最好還是選擇int而不要選擇varchar 11 table list 欄位名 型別 id int auto increment list id varchar...

MySQL中Order By實現原理分析

下面將通過例項分析兩種排序實現方式及實現 假設有 table a 和 b 兩個表結構分別如下 1 利用有序索引進行排序,實際上就是當我們query 的order by 條件和query 的執行計畫中所利用的index的索引鍵 或前面幾個索引鍵 完全一致,且索引訪問方式為rang ref 或者inde...

mysql的order by導致很慢

mysql的order by導致很慢 解決方法 我解決的方法是使用force index強制使用索引,為tcug datetime欄位新建乙個名字為tcug datetime的索引 normal btree selecta.tcug datetime from manage.tb crm files...