MySQL簡單瞭解“order by”是怎麼工作的
針對排序來說,order by 是我們使用非常頻繁的關鍵字。結合之前我們對索引的瞭解再來看這篇文章會讓我們深刻理解在排序的時候,是如何利用索引來達到少掃描表或者使用外部排序的。
先定義一個表輔助我們後面理解:
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=xxx 可以使用到我們定義的一個索引。但是 order by name 明顯我們沒有索引,所以肯定需要先用索引查詢到 city=xxx 然後再進行回表查詢,最後再排序。
全欄位排序
在 city 欄位上面建立索引之後,我們使用執行計劃來檢視這個語句
可以看到有索引的情況下 我們這裡還是使用了 "Using filesort" 表示需要排序,MySQL 會給每個執行緒分配一塊記憶體用於排序 稱為 sort_buffer。
我們在執行上面 select 語句的時候通常經歷了這樣一個過程
1. 初始化 sort_buffer,確認放入 name,city,age 這三個欄位。
2. 從索引 city 找到第一個滿足 city='杭州'條件的主鍵 id。
3. 回表取到 name,age 三個欄位值,存入 sort_buffer 中。
4. 從索引 city 取下一個主鍵 id 記錄。
5. 重複 3-4 步驟,直到 city 不滿足條件。
6. 對 sort_buffer 中的資料按照欄位 name 做快速排序。
7. 排序結果取前 1000 行返回給客戶端。
這被我們稱為全欄位排序。
按照 name 排序這個動作即可能在記憶體中完成,也可以能使用外部檔案排序。這取決於 sort_buffer_size 。sort_buffer_size 的預設值是1048576 byte 也就是 1M,如果要排序的資料量小於 1m 排序就在記憶體中完成,如果排序資料量大,記憶體放不下,則使用磁碟臨時檔案輔助排序。
Rowid 排序
如果單行很大,需要的欄位全部放進 sort_buffer 效果就不會很好。
MySQL 中專門用於控制排序的行資料長度有個引數 max_length_for_sort_data 預設是1024,如果超過了這個值就會使用 rowid 排序。那麼執行上面語句的流程就變成了
1. 初始化 sort_buffe 確定放入兩個欄位即 name 和 id 。
2. 從索引 city 找到第一個滿足 city = '杭州'條件的主鍵 id。
3. 回表取 name 和 id 兩個欄位 存入 sort_buffer 中。
4. 取下個滿足條件的記錄 重複 2 3 步驟。
5. 對 sort_buffer 中的 name 進行排序。
6.遍歷結果取前 1000 行。然後按照 id 再回一次表取的結果欄位返回給客戶端。
其實並不是所有 oder by 語句都需要進行上面的二次排序操作。從上面分析的執行過程,我們可以注意到。MySQL 之所以需要生成臨時表,是因為要在臨時表上做排序,是因為之前我們取得的是資料是無序的。
如果我們對剛才的索引修改一下,使得他是一個聯合索引,那麼第二個欄位我們拿到的值其實就是有序的了。
聯合索引滿足這麼一個條件,當我們的第一個索引欄位是相等的情況下,第二個欄位是有序的。
這能保證如果我們建立 (city,name) 索引的話,當我們在搜尋 city='杭州'的情況的是時候找到的目標第二個欄位 name 其實是有序的。所以查詢過程可以簡化成。
1. 從索引 (city,name) 找到第一個滿足 city = '杭州'條件的主鍵 id 。
2. 回表取到 name city age 三個值返回。
3. 取下一個 id 。
4. 重複2 3 兩個步驟直到 1000 條記錄,或者是不滿足 city = '杭州'條件結束。
也因為查詢過程都可以使用到索引的有序性,所以不再需要排序也不需要時使用 sort buffer 了。
更近一步的優化就是之前說過的索引覆蓋,將需要查詢的欄位也覆蓋進索引中,再省掉回表的步驟,可以讓整個查詢的速度更快。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。