mysql的order by,group by和distinct優化


order by,group by和distinct三類操作是在mysql中經常使用的,而且都涉及到排序,所以就把這三種操作放在一起介紹。
order by的實現與優化
order by的實現有兩種方式,主要就是按用沒用到索引來區分:
1. 根據索引字段排序,利用索引取出的數據已經是排好序的,直接返回給客戶端;
2. 沒有用到索引,將取出的數據進行一次排序操作后返回給客戶端。
下面通過示例來介紹這兩種方式間的差異,首先利用索引進行order by操作,使用explain分析得出的執行計划:
[sql] view plain copy
EXPLAIN SELECT m.id,m.subject,c.content FROM group_message m,group_message_content c WHERE m.group_id = 1 AND m.id = c.group_msg_id ORDER BY m.user_id\G;
[plain] view plain copy
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: m
type: ref
possible_keys: PRIMARY,idx_group_message_gid_uid
key: idx_group_message_gid_uid
key_len: 4
ref: const
rows: 4
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: c
type: ref
possible_keys: group_message_content_msg_id
key: group_message_content_msg_id
key_len: 4
ref: m.id
rows: 11
Extra:
從執行計划里可以看出,進行了order by操作但是執行計划里並沒有排序操作,因為optimizer對query進行了優化,它會按照m.user_id上的索引順序來訪問數據,這樣獲取的數據已經是排好序的。
這種利用索引實現數據排序的方法是 MySQL 中實現結果集排序的最佳做法,可以完全避免因為排序所帶來的資源消耗。所以,在我們優化 query 語句中的 order by 的時候,盡可能利用已有的索引避免實際的排序計算,可以很大幅度的提升 order by 操作的性能。在有些 query 的優化過程中,為了避免實際的排序操作而調整索引字段的順序,甚至是增加索引字段也是值得的。當然,在調整索前,同時還需要評估調整該索引對其他 query 所帶來的影響,平衡整體得失。
如果沒有用到索引,mysql就會將取出的數據按照一定的排序算法進行排序,然后再把排好序的數據返回給客戶端。mysql中主要使用兩種排序算法:
1. 取出用於排序的條件字段和指向相應數據行的指針,在sort buffer中對條件進行排序,排好序之后利用指針取出數據行中的請求數據,然后返回給客戶端;
2. 取出用於排序的條件字段和其它所有請求數據,將不用於排序的字段存放在一塊內存中,然后在sort buffer中對條件字段進行排序,排好序后利用行指針將在內存中的數據進行匹配合並結果集,然后將排好序的數據返回給客戶端。
第二種排序算法是mysql4.1版本才開始支持的,第一種算法是各個版本都支持的。兩種算法的比較,第二種利用內存減少了數據的二次訪問,節省了IO操作,是一種空間換時間的優化方式。
下面用一個實例來分析不使用索引時的執行計划:
[sql] view plain copy
EXPLAIN SELECT m.id,m.subject,c.content FROM group_message m,group_message_content c WHERE m.group_id = 1 AND m.id = c.group_msg_id ORDER BY m.subject\G;
[plain] view plain copy
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: m
type: ref
possible_keys: PRIMARY,idx_group_message_gid_uid
key: idx_group_message_gid_uid
key_len: 4
ref: const
rows: 4
Extra: Using where; Using filesort
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: c
type: ref
possible_keys: group_message_content_msg_id
key: group_message_content_msg_id
key_len: 4
ref: m.id
rows: 11
Extra:
從執行計划里可以看出,在對group_message進行數據訪問的時候,extra信息里顯示Using filesort,這就表示從group_message獲取數據后需要對數據進行排序操作,然后再利用排序后的結果集來驅動第二個表。
對於更復雜的情況,比如用於排序的字段存在在多個表中,或者在排序之前要先經過join操作,這樣mysql必須先把join的結果集放入一個臨時表,之后再把臨時表中的數據取到sort buffer里進行排序。下面再用一個簡單的實例來分析optimizer給出的執行計划。
[sql] view plain copy
explain select m.id,m.subject,c.content FROM group_message m,group_message_content c
WHERE m.group_id = 1 AND m.id = c.group_msg_id ORDER BY c.content\G;
給出的執行計划:
[plain] view plain copy
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: m
type: ref
possible_keys: PRIMARY,idx_group_message_gid_uid
key: idx_group_message_gid_uid
key_len: 4
ref: const
rows: 4
Extra: Using temporary; Using filesort
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: c
type: ref
possible_keys: group_message_content_msg_id
key: group_message_content_msg_id
key_len: 4
ref: example.m.id
rows: 11
Extra:
可以看見Extra信息顯示Using temporary,這就表示將兩個表的join內容取出並放進到一個臨時表中之后再進行filesort。
通過以上介紹知道order by的優化很簡單,就是讓mysql使用第二種排序算法,這樣可以減少大量的IO操作,提高性能,但是如何做到呢:
1. 加大max_length_for_sort_data參數的設置。mysql通過該參數來決定使用哪種排序算法,當需要取出的所有數據長度小於這個參數的值的時候,mysql將采用第二中改進的排序算法,否則,使用第一種算法,所以只要內存充足就可以設置足夠大的值來讓mysql采用改進的排序算法;
2. 去掉不必要的返回字段,很容易從上一點知道原因;
3. 增大sort_buffer_size參數的值,當mysql對條件字段進行排序時,如果需要排序字段的總長度大於該參數的值的時候,mysql就會對需要排序的字段使用臨時表進行分段,這樣也會有性能的消耗。
group by的實現與優化
group by的實現過程除了要使用排序操作外,還要進行分組操作,如果使用到一些聚合函數,還要進行相應的聚合計算。group by的實現方式根據是否使用到索引分為三種:
1. 使用松散(Loose)索引掃描實現group by,所謂的松散索引掃描,就是mysql不需要掃描所有滿足條件的索引鍵即可完成group by操作,下面通過一個簡單的實例來分析一下這個過程。
[sql] view plain copy
create index idx_gid_uid_gc on group_message(group_id,user_id,gmt_create);
drop index idx_group_message_gid_uid on group_message;
EXPLAIN SELECT user_id,max(gmt_create) FROM group_message WHERE group_id < 10 GROUP BY group_id,user_id\G;
得到的執行計划:
[plain] view plain copy
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: group_message
type: range
possible_keys: idx_gid_uid_gc
key: idx_gid_uid_gc
key_len: 8
ref: NULL
rows: 4
Extra: Using where; Using index for group-by
可以看見Extra信息顯示了Using index for group-by,這就是說mysql使用了松散索引掃描來實現group by操作。
要利用到松散索引掃描實現group by,需要滿足以下幾個條件:
group by條件字段必須在同一個索引中最前面的連續位置;
只能使用max和min這兩個聚合函數;
索引的任何其它部分(除了那些來自查詢中引用的GROUP BY)必須為常數(也就是說,必須按常量數量來引用它們),但min或max 函數的參數例外。
為什么松散索引的效率會高很多?
當沒有where條件,即必須經過全索引掃描的時候,松散索引掃描的鍵值數量與分組的分組量一樣多,也就是比實際存在的鍵值數量小很多。而當有where條件包含范圍判斷式或者等值表達式的時候,松散索引掃描查找滿足范圍條件的每個組的第1個關鍵字。
2. 使用緊湊(Tight)索引掃描實現group by,緊湊索引與松散索引最主要的區別就是在需要掃描索引的時候,緊湊索引讀取所有滿足條件的索引鍵,然后再來使用group by操作得到相應的結果。下面同樣用一個例子來分析。
[sql] view plain copy
EXPLAIN SELECT max(gmt_create) FROM group_message WHERE group_id = 2 GROUP BY user_id\G;
得出的執行計划如下:
[plain] view plain copy
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: group_message
type: ref
possible_keys: idx_group_message_gid_uid,idx_gid_uid_gc
key: idx_gid_uid_gc
key_len: 4
ref: const
rows: 4
Extra: Using where; Using index
從執行計划里看見extra信息顯示Using index而不是Using index for group by,意味着需要訪問where條件所限定的所有索引鍵信息之后才能得出結果。optimizer首先會嘗試通過松散索引掃描來完成group by操作,當發現有些情況(當group by 條件字段不連續或者不是索引前綴部分的時候,optimizer無法使用松散索引掃描)不滿足松散索引時,才會選擇緊湊索引掃描來實現。
3. 使用臨時表實現group by
group by操作想要利用索引,必須滿足group by字段必須同時存放於同一個索引中,且該索引是一個有序索引,而且,使用不同的聚合函數也會影響是否使用索引來實現group by操作。當optimizer無法找到合適的索引可以利用的時候,就會選擇將讀取的數據放入臨時表中來完成group by操作。下面通過一個簡單的例子來演示這個過程。
[sql] view plain copy
EXPLAIN SELECT max(gmt_create) FROM group_message WHERE group_id > 1 and group_id < 10 GROUP BY user_id\G;
得到的執行計划如下:
[plain] view plain copy
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: group_message
type: range
possible_keys: idx_group_message_gid_uid,idx_gid_uid_gc
key: idx_gid_uid_gc
key_len: 4
ref: NULL
rows: 32
Extra: Using where; Using index; Using temporary; Using filesort
從執行計划的extra信息中可以看見,對這次的group by操作進行了使用臨時表(Using temporary)然后進行了排序操作才完成的。因為group_id並不是一個常量,而是一個范圍,並且group by的字段為user_id,mysql無法根據索引的順序來完成group by的實現,只能先通過索引范圍掃描得到需要的數據,然后將數據放入一個臨時表,然后再進行排序和分組操作最終完成group by操作。
上面介紹了實現group by操作的三種方式,可以得出以下幾點用於group by優化:
1. 盡可能利用索引並且是松散索引來完成group by操作,這的依靠調整索引或者調整query來實現;
2. 當無法利用索引的時候,必須要提供足夠的sort_buffer_size來供mysql完成排序操作,之前介紹過,不然mysql會將需要排序的字段進行分段排序,會影響性能。除此之外盡量不要對大結果集進行group by操作,因為一旦數據量超過系統最大臨時表大小時,mysql會將臨時表里的數據copy到磁盤上然后再進行操作,性能會成數量級的下降。
distinct的實現與優化
distinct的實現原理同group by類似,實現過程只是在group by之后只取出每一組中的第一條記錄,所以distinct同樣可以利用松散或者緊湊索引來實現,不同的是,當無法利用索引實現distinct時,mysql同樣會將數據取出放進一個臨時表,不過不會對臨時表進行排序操作。下面同樣通過一些簡單的例子來顯示其實現過程。
1.使用松散索引完成distinct操作:
[sql] view plain copy
EXPLAIN SELECT DISTINCT group_id FROM group_message\G;
得到的執行計划如下:
[plain] view plain copy
*************************** 1. row ***************************
id: 1
SELECT_type: SIMPLE
table: group_message
type: range
possible_keys: NULL
key: idx_gid_uid_gc
key_len: 4
ref: NULL
rows: 10
Extra: Using index for group-by
從extra信息里可以看見Using index for group by,意味着mysql使用了松散索引來完成group by操作,然后取出每組中的第一條數據來完成distinct操作。
2. 使用緊湊索引完成distinct操作:
[sql] view plain copy
EXPLAIN SELECT DISTINCT user_id FROM group_message where group_id =2\G;
得到的執行計划如下:
[plain] view plain copy
*************************** 1. row ***************************
id: 1
SELECT_type: SIMPLE
table: group_message
type: ref
possible_keys: idx_gid_uid_gc
key: idx_gid_uid_gc
key_len: 4
ref: const
rows: 4
Extra: Using WHERE; Using index
extra信息顯示Using index,說明使用了緊湊索引。mysql讓存儲引擎掃描group_id=2的所有索引鍵,得出所有的user_id,因為是聯合索引,所以取出的user_id已經是排好序的,對相同的user_id取出一條記錄即完成了本次distinct操作。
3. 無法利用索引完成distinct操作:
[sql] view plain copy
EXPLAIN SELECT DISTINCT user_id FROM group_message WHERE group_id > 1 AND group_id < 10\G;
得到的執行計划如下:
[plain] view plain copy
*************************** 1. row ***************************
id: 1
SELECT_type: SIMPLE
table: group_message
type: range
possible_keys: idx_gid_uid_gc
key: idx_gid_uid_gc
key_len: 4
ref: NULL
rows: 32
Extra: Using WHERE; Using index; Using temporary
從extra信息看出,mysql使用的臨時表,但並沒有進行排序操作。
4. 同時使用distinct和group by:
[sql] view plain copy
EXPLAIN SELECT DISTINCT max(user_id) FROM group_message WHERE group_id > 1 AND group_id < 10 GROUP BY group_id\G;
得到的執行計划如下:
[plain] view plain copy
*************************** 1. row ***************************
id: 1
SELECT_type: SIMPLE
table: group_message
type: range
possible_keys: idx_gid_uid_gc
key: idx_gid_uid_gc
key_len: 4
ref: NULL
rows: 32
Extra: Using WHERE; Using index; Using temporary; Using filesort
從extra里看出mysql使用了排序操作,因為進行了group by操作。
因為distinct的實現原理同group by類似,所以優化手段也一樣,盡量使用索引,無法使用索引的時候,確保不要在大結果集上進行distinct操作,磁盤上的IO操作和內存中的IO操作性能完全不是一個數量級的差距。


免責聲明!

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



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