MySQL文件排序算法介紹

前言

排序是數據庫中的一個基本功能,MySQL也不例外。通過Order by語句即能達到將指定的結果集排序的目的,

其實不僅僅是Order by語句,Group by語句,Distinct語句都會隱含使用排序

在實際業務場景中,SQL的執行計劃中會出現“Using filesort”,這裏需要注意的是filesort並不意味着就是文件排序,其實也有可能是內存排序,這個主要由sort_buffer_size參數與結果集大小確定

如果內置內存臨時表創建後變得太大,MySQL會自動將它轉換成磁盤臨時表。
內存臨時表的大小取決與 tmp_table_size參數和max_heap_table_size參數的值。用 CREATE TABLE 產生的內存臨時表的大小取決與 max_heap_table_size來決定是否要將其轉換成磁盤臨時表
當使用內存臨時表時,CREATED_TMP_TABLES狀態變量值會增加,使用磁盤臨時表時,CREATED_TMP_DISK_TABLES的狀態值會增加。

首先介紹MySQL實現排序的內部原理,並介紹與排序相關的參數及排序一致問題

1、排序實現的算法:

對於不能利用索引避免排序的 SQL,數據庫不得不自己排序功能以滿足業務需求,執行計劃中會出現"USING TEMPORARY; USING filesort",

有時候filesore並不意味着就是文件排序也有可能是內存排序,只有由參數sort_buffer_size和結果集大小確定。

MySQL內部排序主要有3種方式:常規排序、優化排序和優先隊列排序,假設表結構如下:

CREATE TABLE `t1` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `col1` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,

  `col2` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,

  `col3` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,

  PRIMARY KEY (`id`),

  KEY `col1` (`col1`,`col2`)

) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

SELECT col1,col2,col3 FROM t1 WHERE col1="100" ORDER BY col2;

    a.常規排序(單路排序)

單路排序:是一次性取出滿足條件行的所有字段,然後在sort buffer中進行排序

(1).從表t1中獲取滿足WHERE條件的記錄

(2).對於每條記錄,將記錄的主鍵+排序鍵(id,col2)取出放入sort buffer

(3).如果sort buffer可以存放所有滿足條件的(id,col2)對,則進行排序;否則sort buffer滿後,進行排序並固化到臨時文件中。(排序算法採用的是快速排序算法)

(4).若排序中產生了臨時文件,需要利用歸併排序算法,保證臨時文件中記錄是有序的

(5).循環執行上述過程,直到所有滿足條件的記錄全部參與排序

(6).掃描排好序的(id,col2)對,並利用id去撈取SELECT需要返回的列(col1,col2,col3)

(7).將獲取的結果集返回

從上述流程來看,是否使用文件排序主要看sort buffer是否能容下需要排序的(id,col2)對,這個buffer的大小由sort_buffer_size參數控制。此外一次排序需要兩次IO,一次是撈(id,col2),第二次是撈(col1,col2,col3),由於返回的結果集是按col2排序,因此id是亂序的,通過亂序的id去撈(col1,col2,col3)時會產生大量的隨機IO。對於第二次MySQL本身一個優化,

即在撈之前首先將id排序,並放入緩衝區,這個緩存區大小由參數read_rnd_buffer_size控制,然後有序去撈記錄,將隨機IO轉爲順序IO

    b.優化排序(雙路排序)

雙路排序:是首先根據相應的條件取出相應的排序字段和可以直接定位行數據的行指針信息,然後在sort buffer 中進行排序

常規排序方式除了排序本身,還需要額外兩次IO。優化的排序方式相對於常規排序,減少了第二次IO。主要區別在於,放入sort buffer不是(id,col2),而是(col1,col2,col3)。由於sort buffer中包含了查詢需要的所有字段,因此排序完成後可以直接返回,無需二次撈數據。這種方式的代價在於,同樣大小的sort buffer,能存放的(col1,col2,col3)數目要小於(id,col2),如果sort buffer不夠大,可能導致需要寫臨時文件,造成額外的IO。當然MySQL提供了參數max_length_for_sort_data,

只有當排序元組小於max_length_for_sort_data時,才能利用優化排序方式,否則只能用常規排序方式

    c.優先隊列排序

爲了得到最終的排序結果,無論怎樣,我們都需要將所有滿足條件的記錄進行排序才能返回。那麼相對於優化排序方式,

在空間層面做了優化黑盒加入了一種新的排序方式--優先隊列,這種方式採用堆排序實現,堆排序算法特徵正好可以解limit M,N 這類排序的問題,雖然仍然需要所有元素參與排序,但是只需要M+N個元組的sort buffer空間即可,對於M,N很小的場景,基本不會因爲sort buffer不夠而導致需要臨時文件進行歸併排序的問題。

對於升序,採用大頂堆,最終堆中的元素組成了最小的N個元素,對於降序,採用小頂堆,最終堆中的元素組成了最大的N的元素


2、排序優化與索引使用

爲了優化SQL語句的排序性能,最好的情況是避免排序,合理利用索引是一個不錯的方法。

因爲索引本身也是有序的,如果在需要排序的字段上面建立了合適的索引,那麼就可以跳過排序的過程,提高SQL的查詢速度,\

通過一些典型SQL說明哪些可以利用索引減少排序,哪些不能,

1、select * from t1 order by col1,col2

2、select * from t1 where  col1="100" order by col2

3、select *from t1 col1>"100" order by col1 asc

4、select * from  t1 where col1="100" and col2>"100" order by col2

3、不能利用索引避免排序

通過索引掃描的記錄數超過30%,變全表掃描

聯合索引中,第一索引列使用範圍查詢

聯合索引中,第一查詢條件不是最左索引列

升降序不一致無法使用

排序字段在多個索引中無法使用(一個聯合索引一個單列索引,一條SQL一次只能使用一個索引)

排序字段是單獨的列無法使用索引

4、業務案例,添加合理的索引

    1、業務DDL:

    

1f5fb5f142629c02c7efb697bc46ef1d.png-wh_

06f3153a1a582d3ad6bfa310c286ec7b.png-wh_

    2、對原SQL執行計劃的查看:

    1871f04f1964d545b24abe9722df7b3e.png-wh_

    3、優化後的SQL執行計劃-1

    8dfedf5f5756bfad94aeb9a938066a7e.png-wh_

   3、優化後的SQL執行計劃-2

    661be3b0baac759ca2799db8f8dc8677.png-wh_

優化Filesort

當無法避免排序操作時,又該如何來優化呢?很顯然,應該儘可能讓 MySQL 選擇使用第二種單路算法來進行排序。這樣可以減少大量的隨機IO操作,很大幅度地提高排序工作的效率。

1. 加大 max_length_for_sort_data 參數的設置

在 MySQL 中,決定使用老式排序算法還是改進版排序算法是通過參數 max_length_for_ sort_data 來決定的。當所有返回字段的最大長度小於這個參數值時,MySQL 就會選擇改進後的排序算法,反之,則選擇老式的算法。所以,如果有充足的內存讓MySQL 存放須要返回的非排序字段,就可以加大這個參數的值來讓 MySQL 選擇使用改進版的排序算法。

2. 去掉不必要的返回字段

當內存不是很充裕時,不能簡單地通過強行加大上面的參數來強迫 MySQL 去使用改進版的排序算法,否則可能會造成 MySQL 不得不將數據分成很多段,然後進行排序,這樣可能會得不償失。此時就須要去掉不必要的返回字段,讓返回結果長度適應 max_length_for_sort_data 參數的限制。

3. 增大 sort_buffer_size 參數設置

增大 sort_buffer_size 並不是爲了讓 MySQL選擇改進版的排序算法,而是爲了讓MySQL儘量減少在排序過程中對須要排序的數據進行分段,因爲分段會造成 MySQL 不得不使用臨時表來進行交換排序。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章