從排序原理到MYSQL中的排序方式

本文參考MySQL官方文檔,算法書籍,部分為自己觀點可能有誤,如果有誤請指出共同討論
轉(zhuǎn)載請說明出處,謝謝!


一、MYSQL排序可能用到的排序算法

從MYSQL官方文檔和源碼的接口來看MYSQL使用BUFFER內(nèi)部快速排序算法,外部多路歸并排序算法,相應(yīng)的接口函數(shù)為
filesort()函數(shù),注意filesort()是一個總的接口,內(nèi)部排序?qū)嶋H調(diào)用save_index()下的std::stable_sort\std::sort、歸并排序
也包含在下面接口可能為merge_many_buff(),也就像執(zhí)行計劃中using filesort的含義,他只能代表使用了排序,但是
是否使用到tempfile排序是看不出來的,但是這個可以再trace看到但是線上是不可以trace的研究是可以的,隨后我會演示。
還要注意using temporary只是說明使用了臨時表存儲中間結(jié)果,這里先不討論,只看排序。

下面簡要介紹兩種算法原理

1、buffer內(nèi)部 快速排序算法
   快速排序是交換排序類算法,是冒泡排序的升級版,其原理是采用分而治之的思想,在于每趟交換設(shè)置一個基準點,
   將大于這個基準點的數(shù)據(jù)放到一邊,大于的放到另一邊,不斷的進行遞歸完成,對于大數(shù)據(jù)量的排序快速排序一般
   效率優(yōu)秀,在文章的最后是一個簡單的快速排序的實現(xiàn),如果對算法感興趣的可以參考一下。但是至少還能進
   行3種優(yōu)化
   --小數(shù)據(jù)優(yōu)化,因為快速排序?qū)?shù)據(jù)量小的時候并不是最優(yōu),可以使用其他排序算法如插入排序。
   --尾遞歸優(yōu)化,減少棧的使用
   --基準點優(yōu)化,盡量取到數(shù)據(jù)中的中間值作為基準點,這樣能夠讓快速排序更加優(yōu)化。
   
2、外部磁盤多路歸并排序
   將內(nèi)部快速排序后有序的數(shù)據(jù)文件進行不斷的比較得到有序的文件,每次歸并多少個文件就是歸并的路數(shù)
  圖中每一個R當然代表的一個有序的磁盤文件
   下圖2路歸并排序(截取數(shù)據(jù)結(jié)構(gòu)C語言版)
   從排序原理到MYSQL中的排序方式
   下圖5路歸并排序(截取數(shù)據(jù)結(jié)構(gòu)C語言版)
從排序原理到MYSQL中的排序方式

二、MYSQL相關(guān)參數(shù)
    sort_buffer_size:
        當然也就是每次排序的buffer,用作內(nèi)部快速排序用,如果buffer越大當然產(chǎn)生的物理文件也就越少,但是這個
    參數(shù)是會話級別的,過分加大會造成內(nèi)存不足,默認256K。注意:
    On Linux, there are thresholds of 256KB and
    2MB where larger values may significantly slow down memory allocation, so you should consider staying
    below one of those values
    
    read_rnd_buffer_size:
        除了MRR用到,這里也用到了用于 二次排序的時候?qū)ε判蚝玫臄?shù)據(jù)按照primary key(ROW_ID)按照分塊的方式再次排序,
    意義同樣在回表取數(shù)據(jù)可以盡量順序化
    
    max_length_for_sort_data:
        單位為字節(jié)(bytes),如果排序返回行的字段長度綜合大約這個值,使用二次排序而不是一次排序,默認1024,最小
    值為4,如果加大這個值可能過多的使用一次排序造成高TEMPFILE空間使用而CPU不高,為什么如此后面解釋。
    
    max_sort_length:
        單位為字節(jié)(bytes),如果排序字段的長度超過這個值,只是用這個參數(shù)設(shè)置的長度,默認1024,比如text這種字段
    經(jīng)常會大于這個值,如果加大這個值明顯會提高排序的準確性,但是也意味著更高的BUFFER使用和TEMPFILE使用
    
三、監(jiān)控磁盤排序
    Sort_merge_passes:磁盤排序歸并次數(shù),減少sort_buffer_size大小會顯著減少Sort_merge_passes值,并且臨時文件也
    會變少,第五部分證明
    sys.statements_with_sorting視圖
     
四、MYSQL二次訪問排序(original method)和一次訪問排序(modified method)

1、二次訪問排序
--讀取數(shù)據(jù)只包含排序鍵值和rowid(primary key)放到sort buffer
--在buffer中進行快速排序,如果buffer 滿則把內(nèi)存中的排序數(shù)據(jù)寫入tempfile
--重復上面的過程直到內(nèi)部快速排序完成,并且生成多個tmepfile文件
--進行多路歸并排序源碼接口在merge_many_buff(),其中定義了MERGEBUFF,MERGEBUFF2 2個宏
  這個在官方文檔上有出現(xiàn)所以提出來說明一下
  /* Defines used by filesort and uniques */
#define MERGEBUFF 7
#define MERGEBUFF2 15
  如果有興趣的可以仔細看看源碼..
--最后一次歸并的時候,只有rowid(priamry key)到最后的文件中
--對最后的文件根據(jù)rowid(primary key)訪問表數(shù)據(jù),這樣就可以得到排序好的數(shù)據(jù)
  這里有一個類似MRR的優(yōu)化,將數(shù)據(jù)進行分塊讀入read_rnd_buffer_size進行
  按照rowid(primary key)排序在去訪問表的數(shù)據(jù),目的在于減少隨機讀取造成的影響
  但是這是分塊的,只能減少不能杜絕,特別是數(shù)據(jù)量特別大的情況下,因為
  read_rnd_buffer_size只有默認256K.

問題在于對表數(shù)據(jù)的二次訪問,一次在讀取數(shù)據(jù)的時候,后一次在通過排序好的
rowid(primary key)進行數(shù)據(jù)的訪問,并且會出現(xiàn)大量隨機訪問。

2、一次訪問排序
這個就簡單了,二次訪問排序是把排序鍵值和rowid(primary key)放到sort buffer,
這個就是關(guān)于需要的數(shù)據(jù)字段全部放到sort buffer比如:
select id,name1,name2 from test order by id;

這里id,name1,name2都會存放到sort buffer中。這樣排序好就好了,不需要回表取
數(shù)據(jù)了,當然這樣做的劣勢就是更大的sort buffer占用,更大tempfile占用。所以
mysql使用max_length_for_sort_data來限制默認1024,這是指id,name1,name2字段
的bytes長度。
因為不需要回表,所以只要一次訪問數(shù)據(jù)

3、5.7.3后一次訪問排序算法的優(yōu)化
使用一個叫做pack優(yōu)化的方法,目的在于壓縮NULL減少一次訪問排序算法對sort buffer和tempfile的過多使用
原文:
without packing, a VARCHAR(255)
column value containing only 3 characters takes 255 characters in the sort buffer. With packing, 
the value requires only 3 characters plus a two-byte length indicator. NULL values require only 
a bitmask.
但是我在做MYSQL TRACE的時候發(fā)現(xiàn)這還有一個unpack的過程,并且每一行每一個字段都需要pack unpack
隨后證明

關(guān)于使用了的那種排序方式在執(zhí)行計劃中都體現(xiàn)為filesort不好弄清楚,但是我們可以通過trace的方式,
在官方文檔也說了,但是我使用了對MYSQLD的trace方式來做,效果一致,詳細參考第五部分

五、證明觀點

1、首先需要證明是使用的是二次訪問排序還是一次訪問排序,是否啟用了pack
官方文檔說明
"filesort_summary": {
"rows": 100,
"examined_rows": 100,
"number_of_tmp_files": 0,
"sort_buffer_size": 25192,
"sort_mode": ""
}
sort_mode:
: This indicates use of the original algorithm. 
: This indicates use of the modified algorithm.
: This indicates use of the modified algorithm. Sort
buffer tuples contain the sort key value and packed columns referenced by the query. 
也就是說
sort_key, rowid是二次訪問排序
sort_key, additional_fields是一次訪問排序
sort_key, packed_additional_fields是一次訪問排序+pack方式
好了我們來證明,使用測試表
mysql> show create table testmer \G;
*************************** 1. row ***************************
       Table: testmer
Create Table: CREATE TABLE `testmer` (
  `seq` int(11) NOT NULL,
  `id1` int(11) DEFAULT NULL,
  `id2` int(11) DEFAULT NULL,
  `id3` int(11) DEFAULT NULL,
  `id4` int(11) DEFAULT NULL,
  PRIMARY KEY (`seq`),
  KEY `id1` (`id1`,`id2`),
  KEY `id3` (`id3`,`id4`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.01 sec)

mysql>  select * from testmer ;
+-----+------+------+------+------+
| seq | id1  | id2  | id3  | id4  |
+-----+------+------+------+------+
|   1 |    1 |    2 |    4 |    4 |
|   2 |    1 |    3 |    4 |    5 |
|   3 |    1 |    2 |    4 |    4 |
|   4 |    2 |    4 |    5 |    6 |
|   5 |    2 |    6 |    5 |    8 |
|   6 |    2 |   10 |    5 |    3 |
|   7 |    4 |    5 |    8 |   10 |
|   8 |    0 |    1 |    3 |    4 |
+-----+------+------+------+------+
8 rows in set (0.01 sec)

分別在max_length_for_sort_data為1024和max_length_for_sort_data為4對
select * from testmer order by id1;
生成trace文件
意義也就是使用一次訪問排序和二次訪問排序,因為數(shù)據(jù)量少也就在sort_buffer
排序就好了。

一次訪問排序:
opt: filesort_execution: ending struct
opt: filesort_summary: starting struct
opt: rows: 8
opt: examined_rows: 8
opt: number_of_tmp_files: 0
opt: sort_buffer_size: 34440
opt: sort_mode: ""
opt: filesort_summary: ending struct

為 一次訪問排序+pack方式

二次訪問排序:
opt: filesort_execution: ending struct
opt: filesort_summary: starting struct
opt: rows: 8
opt: examined_rows: 8
opt: number_of_tmp_files: 0
opt: sort_buffer_size: 18480
opt: sort_mode: ""
opt: filesort_summary: ending struct

為是二次訪問排序
可以看到不同,證明了max_length_for_sort_data的作用

其實這個是filesort()函數(shù)中的一個調(diào)用而已,其實gdb也可以打上斷點也能看到
  Opt_trace_object(trace, "filesort_summary")
    .add("rows", num_rows)
    .add("examined_rows", param.examined_rows)
    .add("number_of_tmp_files", num_chunks)
    .add("sort_buffer_size", table_sort.sort_buffer_size())
    .add_alnum("sort_mode",
               param.using_packed_addons() ?
               "" :
               param.using_addon_fields() ?
               "" : "");

2、證明減少sort_buffer_size大小會顯著減少Sort_merge_passes值,并且臨時文件也
   會變少

 為了完成這個證明我建立了一個大表,降低先sort_buffer為使用如下的語句使用更多的
 tempfile進行排序
 
mysql> select count(*) from  testshared3;
+----------+
| count(*) |
+----------+
|  1048576 |
+----------+
1 row in set (28.31 sec)
 
mysql> set sort_buffer_size=50000;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like '%sort_buffer_size%';
+-------------------------+---------+
| Variable_name           | Value   |
+-------------------------+---------+
| sort_buffer_size        | 50000   |
+-------------------------+---------+

 mysql> show status like '%Sort_merge%';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Sort_merge_passes | 0     |
+-------------------+-------+
1 row in set (0.00 sec)

mysql> explain select * from  testshared3 order by id limit 1048570,1;
+----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| id | select_type | table       | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra          |
+----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+----------------+
|  1 | SIMPLE      | testshared3 | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1023820 |   100.00 | Using filesort |
+----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+----------------+
mysql> select * from  testshared3 order by id limit 1048570,1;
+------+
| id   |
+------+
|    1 |
+------+
1 row in set (5 min 4.76 sec)
完成后
mysql> show status like '%Sort_merge%';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Sort_merge_passes | 63    |
+-------------------+-------+
1 row in set (0.21 sec)

opt: number_of_tmp_files: 378 
臨時文件數(shù)量378

然后加大sort_buffer_size

mysql>  show variables like '%sort_buffer_size%';
+-------------------------+---------+
| Variable_name           | Value   |
+-------------------------+---------+
| sort_buffer_size        | 262144  |
+-------------------------+---------+

mysql> show status like '%Sort_merge%';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Sort_merge_passes | 0     |
+-------------------+-------+
1 row in set (0.04 sec)

還是同樣的語句

mysql> select * from  testshared3 order by id limit 1048570,1;
+------+
| id   |
+------+
|    1 |
+------+
1 row in set (5 min 4.76 sec)
mysql> show status like '%Sort_merge%';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Sort_merge_passes | 11    |
+-------------------+-------+
opt: number_of_tmp_files: 73
臨時文件數(shù)量73

得到證明

3、證明有pack和unpack操作,并且每一行每一個字段都需要pack unpack

這個直接查看trace文件是否有接口就好了
實際上可以看到8段如下的操作
>Field::unpack      
Field::unpack
Field::unpack
Field::unpack
Field::unpack
Field::unpack     
Field::unpack"|wc -l
40                                                              
[root@testmy tmp]# cat sortys2.trace|grep ">Field::pack"|wc -l  
40             

剛好是5(字段)*8(行)               

當然我隨后對一個大表只有一個字段的表進行一樣的測試,既然是一個字段使用
一次訪問排序的時候排序的全部字段就是這個字段而已,所以pack和unpack的次數(shù)應(yīng)該
和行數(shù)差不多
mysql> select count(*) from  testshared3;
+----------+
| count(*) |
+----------+
|  1048576 |
+----------+
1 row in set (28.31 sec)
mysql> desc testshared3;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | YES  |     | NULL    |       |
+-------+---------+------+-----+---------+-------+
1 row in set (0.26 sec)

[root@testmy tmp]# cat mysqld11.trace|grep ">Field::unpack"|wc -l
1048571                    

也得到同樣基本相同的結(jié)構(gòu),證明了一次訪問排序中每一行每一個字段都需
要pack、unpack操作,其實在整個trace中還能看到很多類容比如列取出了
一次訪問排序的全部字段,這里就不在詳解了

六、源碼GDB發(fā)現(xiàn)內(nèi)部排序調(diào)用STD容器的std::stable_sort std::sort 進行排序

(gdb) n
250         if (param->sort_length < 10)
(gdb) list
245         than quicksort seems to be somewhere around 10 to 40 records.
246         So we're a bit conservative, and stay with quicksort up to 100 records.
247       */
248       if (count <= 100)
249       {
250         if (param->sort_length < 10)
251         {
252           std::sort(m_sort_keys, m_sort_keys + count,
253                     Mem_compare(param->sort_length));
254           return;

這部分mysql上的源碼

點擊(此處)折疊或打開

成都創(chuàng)新互聯(lián)主要從事網(wǎng)站設(shè)計制作、網(wǎng)站設(shè)計、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)猇亭,十載網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18980820575

  1. /*
  2.     std::stable_sort has some extra overhead in allocating the temp buffer,
  3.     which takes some time. The cutover point where it starts to get faster
  4.     than quicksort seems to be somewhere around 10 to 40 records.
  5.     So we're a bit conservative, and stay with quicksort up to 100 records.
  6.   */
  7.   if (count <= 100)
  8.   {
  9.     if (param->sort_length < 10)
  10.     {
  11.       std::sort(m_sort_keys, m_sort_keys + count,
  12.                 Mem_compare(param->sort_length));
  13.       return;
  14.     }
  15.     std::sort(m_sort_keys, m_sort_keys + count,
  16.               Mem_compare_longkey(param->sort_length));
  17.     return;
  18.   }
  19.   // Heuristics here: avoid function overhead call for short keys.
  20.   if (param->sort_length < 10)
  21.   {
  22.     std::stable_sort(m_sort_keys, m_sort_keys + count,
  23.                      Mem_compare(param->sort_length));
  24.     return;
  25.   }
  26.   std::stable_sort(m_sort_keys, m_sort_keys + count,
  27.                    Mem_compare_longkey(param->sort_length));




最后附上快速排序的代碼
帶排序數(shù)據(jù)是13,3,2,9,34,5,102,90,20,2
排序完成后如下:
gaopeng@bogon:~/datas$ ./a.out 
sort result:2 2 3 5 9 13 20 34 90 102 

點擊(此處)折疊或打開

  1. /*************************************************************************
  2.   > File Name: qsort.c
  3.   > Author: gaopeng QQ:22389860
  4.   > Mail: gaopp_200217@163.com
  5.   > Created Time: Fri 06 Jan 2017 03:04:08 AM CST
  6.  ************************************************************************/


  7. #include<stdio.h>
  8. #include<stdlib.h>


  9. int partition(int *k,int low,int high)
  10. {

  11.         int point;
  12.         point = k[low]; //基準點,采用數(shù)組的第一個值,這里實際可以優(yōu)化
  13.         while(low<high) //等待low=high一趟交換完成
  14.         {

  15.                 while(low<high && k[high] >=point) //過濾掉尾部大于基準點的值,不需要交換
  16.                 {
  17.                         high--;
  18.                 }
  19.                 k[low] = k[high]; //基準點多次交換為無謂交換直接賦值即可
  20.                 while(low<high && k[low] <=point) //過濾掉頭部小于基準點的值,不需要交換
  21.                 {

  22.                         low++;
  23.                 }
  24.                 k[high] = k[low]; //基準點多次交換為無謂交換直接賦值即可
  25.         }
  26.         k[low] = point;
  27.         return low;
  28. }

  29. int q_sort(int *k,int low,int high)
  30. {

  31.         int point;
  32.         if(low<high)
  33.         {

  34.                 point = partition(k,low,high);
  35.                 q_sort(k,low,point-1); //實現(xiàn)遞歸前半部分
  36.                 q_sort(k,point+1,high); //實現(xiàn)遞歸后半部分
  37.         }
  38.         return 0;
  39. }

  40. int main()
  41. {

  42.         int i,a[10]={13,3,2,9,34,5,102,90,20,2}; //測試數(shù)據(jù)
  43.         q_sort(a,0,9); //數(shù)組下標0 9

  44.         printf("sort result:");
  45.         for(i=0;i<10;i++)
  46.         {
  47.                 printf("%d ",a[i]);
  48.         }
  49.         printf("\n");
  50. }


網(wǎng)頁題目:從排序原理到MYSQL中的排序方式
文章源于:http://muchs.cn/article30/ihjiso.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站建站公司、搜索引擎優(yōu)化、網(wǎng)站設(shè)計公司企業(yè)網(wǎng)站制作、網(wǎng)站排名

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司