第二十二節(jié)、“order by”是怎么工作的?

order by 有兩種排序方式:全字段排序、rowid排序。

1、全字段排序

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;

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

以上語句在city上添加索引后,執(zhí)行以上的查詢語句的執(zhí)行過程如下:

1、初始化sort_buffer,確定放入name、city、age這三個字段;

2、從索引city找到第一個滿足city=‘杭州’ 條件的主鍵id;

3、到主鍵id索引取出整行,取name、city、age三個字段的值,存入sort_buffer中;

4、從索引city取下一個記錄的主鍵id;

5、重復(fù)步驟3、4直到city的值不滿足查詢條件為止;

6、對sort_buffer中的數(shù)據(jù)按照字段name做快速排序;

7、按照排序結(jié)果取前1000行返回給客戶端。


全字段排序

圖中,“按name排序”這個動作,可能在內(nèi)存中完成,也可能需要使用外部排序,這取決于排序所需要的內(nèi)存和參數(shù)sort_buffer_size.

sort_buffer_size,就是MySQL為排序開辟的內(nèi)存(sort_buffer)的大小。如果要排序的數(shù)據(jù)量小于sort_buffer_size,排序就在內(nèi)存中完成。但如果排序數(shù)據(jù)量太大,內(nèi)存放不下,則不得不利用磁盤臨時文件輔助排序。

可以用以下的方法來確定是否使用了臨時排序:

/* 打開 optimizer_trace,只對本線程有效 */

SET optimizer_trace='enabled=on';

/* @a 保存 Innodb_rows_read 的初始值 */

select VARIABLE_VALUE into @a from? performance_schema.session_status where variable_name = 'Innodb_rows_read';

/* 執(zhí)行語句 */

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

/* 查看 OPTIMIZER_TRACE 輸出 */

SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G

/* @b 保存 Innodb_rows_read 的當(dāng)前值 */

select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = 'Innodb_rows_read';

/* 計算 Innodb_rows_read 差值 */

select @b-@a;


這個方法是通過查看OPTIMIZER_TRACE的結(jié)果來確認的,從number_of_tmp_files中看到是否使用了臨時文件。number_of_tmp_files表示的是,排序過程中使用的臨時文件數(shù)。

內(nèi)存放不下時,就需要使用外部排序,外部排序一般使用歸并排序算法。上圖的結(jié)果中可以簡單理解:MySQL將需要排序的數(shù)據(jù)分成12份,每一份單獨排序后存在這些臨時文件中。然后把這12個有序文件再合并成一個有序的大文件。

如果sort_buffer_size超過了需要排序的數(shù)據(jù)量的大小,number_of_tmp_files就是0,表示排序可以直接在內(nèi)存中完成。否則就需要放在臨時文件中排序。sort_buffer_size越小,需要分成的份數(shù)越多,number_of_tmp_files的值就越大。



rowid排序

在全字段排序算法里面,只對原表的數(shù)據(jù)讀了一遍,剩下的操作都是在sort_buffer和臨時文件中執(zhí)行的。但這個算法有一個問題,就是如果查詢要返回的字段很多的話,那么sort_buffer里面要放的字段數(shù)太多,這樣內(nèi)存里能夠同時放下的行數(shù)很少,要分成很多個臨時文件,排序的性能會很差。

如果單行很大,這個方法效率不夠好,那么如果MySQL認為排序的單行長度太大會怎么做尼?

SET max_length_for_sort_data = 16;

max_length_for_sort_data,是MySQL中專門控制用于排序的行數(shù)據(jù)的長度的一個參數(shù)。它的意思是,如果單行的長度超過這個值,MySQL就認為單行太大,要換一個算法。

city、name、age這三個字段的定義總長度是36,把max_length_for_sort_data設(shè)置為16,我們再來看看計算過程有什么改變。

新的算法放入sort_buffer的字段,只有要排序的列(即name字段)和主鍵id。但這時,排序的結(jié)果就因為少了city和age字段的值,不能直接返回了,整個執(zhí)行流程就變成如下所示的樣子:

1、初始化sort_buffer,確定放入兩個字段,即name和id;

2、從索引city找到第一個滿足city=‘杭州’ 條件的主鍵id;

3、到主鍵id索引取出整行,取name、id這兩個字段,存入sort_buffer中;

4、從索引city取下一個記錄的主鍵id;

5、重復(fù)步驟3、4直到不滿足city=‘杭州’ 條件為止;

6、對sort_buffer中的數(shù)據(jù)按照字段name進行排序;

7、遍歷排序結(jié)果,取前1000行,并按照id的值回到原表中取出city、name和age三個字段返回給客戶端。


rowid排序

對比全字段排序流程圖,rowid排序多訪問了一次表t的主鍵索引,就是步驟7.

需要說明的是,最后的“結(jié)果集”是一個邏輯概念,實際上MySQL服務(wù)端從排序后的sort_buffer中依次取出id,然后到原表查到city、name和age這三個字段的結(jié)果,不需要在服務(wù)端再耗費內(nèi)存存儲結(jié)果,是直接返回給客戶端的。


全字段排序與rowid排序的區(qū)別

如果mysql的排序內(nèi)存內(nèi)存太小,會影響排序效率,才會采用rowid排序算法,這樣排序過程中一次可以排序更多行,但是需要再回到原表去取數(shù)據(jù);

如果mysql內(nèi)存足夠大,會優(yōu)先選擇全字段排序,把需要的字段都放到sort_buffer中,這樣排序后就會直接從內(nèi)存里面返回查詢結(jié)果了,不用再回到原表去取數(shù)據(jù)。

對于InnoDB表來說,rowid排序會要求回表多造成磁盤讀,因此不會被優(yōu)先選擇。


是否所有的order by都需要排序操作?

并非所有的order by語句,都需要排序操作的,mysql之所以需要生成臨時表,并且在臨時表上做排序操作,其原因是原來的數(shù)據(jù)都是無序的。

如果能夠保證從city這個索引上取出來的行,天然就是按照name遞增排序的話,是可以不用再排序。

alter table t add index city_user_age(city, name, age);

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

以上的查詢在city, name, age字段上建立了聯(lián)合索引,所以在查詢只有city, name, age字段的查詢語句中,會使用到覆蓋索引,因此這條語句是不需要做排序了的。

覆蓋索引是指,索引上的信息足夠滿足查詢的請求,不需要再回到主鍵索引上去取數(shù)據(jù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容