MySQL-排序相關原理分析


全字段排序和rowId排序

建表語句如下:

CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `city` varchar(16) NOT NULL,
  `name` varchar(16) NOT NULL,
  `age` int(11) NOT NULL,
  `addr` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `city` (`city`)
) ENGINE=InnoDB;

sql語句如下:

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

相關概念定義

sort_buffer:MySQL會給每個線程分配一塊內存區域用於排序,這塊區域叫sort_buffer。如果待排序的數據足夠存放在sort_buffer中,那么就會直接用這塊區域進行排序,算法為快速排序;如果待排序的數據超過了sort_buffer大小,會使用磁盤臨時文件來輔助排序,算法為歸並排序。

全字段排序:sort_buffer中存儲的待排序數據,包括需要返回的所有字段,比如,上面sql語句中的city,name,age,雖然只用name來排序,但是還是冗余存放了city和age的數據,排序完直接返回即可。

rowId排序:sort_buffer中存儲的待排序數據,只包括待排序字段和對應行的主鍵id,比如,上面sql語句,如果使用rowId排序,那么sort_buffer中只會存儲name和rowID字段,等到排序完畢,需要回表查詢出來需要返回的其他字段數據。

什么時候選擇全字段排序?什么時候選擇rowID排序?

當MySQL判斷,當待處理表為InnoDB磁盤表時,會優先使用全字段排序,目的是為了減少rowID排序最后需要再次回表查詢需要返回的字段的操作開銷,但是全字段排序如果需要冗余的單行數據量太大時,就不會選擇全字段排序,而選擇rowID排序。

  • 如何判斷單行數據是否過大?MySQL中會使用max_length_for_sort_data來判斷。

為什么單行數據量大,就需要切換算法?

如果單行數據量太大,內存中能存儲下的行數就會變少,就需要使用更多的磁盤臨時文件來存儲,排序的性能會比較差。


內存臨時表和磁盤臨時表

看這個業務:

有一張單詞表,我們需要隨機顯示三個單詞給用戶。

建表語句和生成數據存儲過程:

mysql> CREATE TABLE `words` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `word` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

delimiter ;;
create procedure idata()
begin
  declare i int;
  set i=0;
  while i<10000 do
    insert into words(word) values(concat(char(97+(i div 1000)), char(97+(i % 1000 div 100)), char(97+(i % 100 div 10)), char(97+(i % 10))));
    set i=i+1;
  end while;
end;;
delimiter ;

call idata();

SQL語句:

mysql> select word from words order by rand() limit 3;

注:rand() 這個函數會返回一個0~1之間的隨機小數值。

當需要使用這個隨機值來排序時,就需要使用臨時表來存儲這個隨機數據。

內存臨時表

執行過程如下:

  1. 首先生成Memory引擎的內存臨時表,在主鍵索引中,依次取出所有的word值,調用rand函數生成 一個隨機值,把word和隨機值存儲到臨時表中。
  2. 然后針對這個臨時表開始排序。使用sort_buffer並且使用rowId算法。
    1. 這里為什么使用rowID算法了呢?因為上面提到過的全字段排序會被優先選擇,前提是待排序的表是磁盤表;現在的待排序表為Memory引擎的內存表,雖然使用rowID,但是最后的回表查詢都是在內存中完成的,開銷大大降低,MySQL當然會選擇可以一次排序更多行的rowId算法。

磁盤臨時表

MySQL中有一個參數,tmp_table_size 這個參數限制了內存臨時表的大小,默認值是 16M。如果臨時表大於16M,就會使用磁盤臨時表來存儲臨時數據,默認是InnoDB引擎表。關於默認的InnoDB引擎表的排序過程,在上面的全字段排序和rowId排序中已經介紹過了。


新的排序算法

上面介紹過,InnoDB磁盤表,要么使用基於全內存的快排,要么基於輔助的磁盤臨時文件的歸並排序,其實在MySQL5.6之后,還引入了一種新的排序算法,優先隊列排序算法。

為什么需要這種算法?

考慮剛才的SQL語句

mysql> select word from words order by rand() limit 3;

無論是使用快排還是歸並排序,他們都是基於所有的數據進行排序。

但分析上面的sql語句,其實我們只需要排序后的前面三條數據,並且后面的排序數據在計算上來說是浪費資源的。有沒有一種算法,可以通過排序,只得到我們需要的最小的三條或者最大的三條數據,並且盡量不使用磁盤臨時文件呢?

優先隊列排序算法

優先隊列排序算法,如果執行上面的sql語句,會先從表中順序取最開始的3條數據,存儲到一個最大堆中(最大堆:堆頭永遠是容器內數據的最大值)。然后遍歷后面的所有數據,判斷當前取值和堆最大值比較,如果比堆最大值小,就把新數據入堆,並且重新排序堆中的順序,保持堆頭為最大值。

經過這種排序以后,堆中就是我們需要的前三個最小值了。

示意圖如下:
優先隊列算法圖解.png

什么時候會選擇這種算法?

當存在limit字句時,並且limit需要的維護的最大堆的大小小於 sort_buffer,就會使用這個算法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM