mysql DISTINCT 的實現與優化


DISTINCT實際上和GROUP BY的操作非常相似,只不過是在GROUP BY之后的每組中只取出一條記錄而已。所以,DISTINCT的實現和GROUP BY的實現也基本差不多,沒有太大的區別。同樣可以通過松散索引掃描或者是緊湊索引掃描來實現,當然,在無法僅僅使用索引即能完成DISTINCT的時候,MySQL只能通過臨時表來完成。但是,和GROUP BY有一點差別的是,DISTINCT並不需要進行排序。也就是說,在僅僅只是DISTINCT操作的Query如果無法僅僅利用索引完成操作的時候,MySQL會利用臨時表來做一次數據的緩存,但是不會對臨時表中的數據進行filesort操作。當然,如果我們在進行DISTINCT的時候還使用了GROUP BY並進行了分組,並使用了類似於MAX之類的聚合函數操作,就無法避免filesort了。

下面我們就通過幾個簡單的Query示例來展示一下DISTINCT的實現。

 

1.首先看看通過松散索引掃描完成DISTINCT的操作:

sky@localhost:  example 11:03:41>EXPLAIN SELECT  DISTINCT group_id

->  FROM group_message\G

***************************1row  ***************************

id1SELECT_type:SIMPLE

table:group_message

type:range

possible_keys:NULL

key:  idx_gid_uid_gc

key_len:4ref:  NULL

rows:10Extra: Using index for  group-by

1  row  in  set  (0.00sec)

 

我們可以很清晰的看到,執行計划中的Extra信息為Usingindex for group-by,這代表什么意思?為什么我沒有進行GROUP BY操作的時候,執行計划中會告訴我這里通過索引進行了GROUP BY呢?其實這就是於DISTINCT的實現原理相關的,在實現DISTINCT的過程中,同樣也是需要分組的,然后再從每組數據中取出一條返回給客戶端。而這里的Extra信息就告訴我們,MySQL利用松散索引掃描就完成了整個操作。當然,如果MySQLQuery Optimizer要是能夠做的再人性化一點將這里的信息換成Using index for distinct那就更好更容易讓人理解了,呵呵。

 

2.  我們再來看看通過緊湊索引掃描的示例:

sky@localhost: example 11:03:53> EXPLAIN SELECT DISTINCT user_id

->FROM group_message

->WHERE group_id = 2\G

***************************1. row ***************************

id:1SELECT_type: SIMPLE

table:group_message

type:ref

possible_keys:idx_gid_uid_gc

key:idx_gid_uid_gc

key_len:4ref: const

rows:4Extra: Using WHERE; Using index

1row in set (0.00 sec)

這里的顯示和通過緊湊索引掃描實現GROUP BY也完全一樣。實際上,這個Query的實現過程中,MySQL會讓存儲引擎掃描group_id=2的所有索引鍵,得出所有的user_id,然后利用索引的已排序特性,每更換一個user_id的索引鍵值的時候保留一條信息,即可在掃描完所有gruop_id=2的索引鍵的時候完成整個DISTINCT操作。

3.下面我們在看看無法單獨使用索引即可完成DISTINCT的時候會是怎樣:

sky@localhost: example 11:04:40> EXPLAIN SELECT DISTINCT user_id

->FROM group_message

->WHERE group_id > 1 AND group_id < 10\G

***************************1. row ***************************

id:1SELECT_type: SIMPLE

table:group_message

type:range

possible_keys:idx_gid_uid_gc

key:idx_gid_uid_gc

key_len:4ref: NULL

rows:32Extra: Using WHERE; Using index; Using temporary

1row in set (0.00 sec)

MySQL無法僅僅依賴索引即可完成DISTINCT操作的時候,就不得不使用臨時表來進行相應的操作了。但是我們可以看到,在MySQL利用臨時表來完成DISTINCT的時候,和處理GROUP BY有一點區別,就是少了filesort。實際上,在MySQL的分組算法中,並不一定非要排序才能完成分組操作的,這一點在上面的GROUP BY優化小技巧中我已經提到過了。實際上這里MySQL正是在沒有排序的情況下實現分組最后完成DISTINCT操作的,所以少了filesort這個排序操作。

4.最后再和GROUP BY結合試試看:

sky@localhost: example 11:05:06> EXPLAIN SELECT DISTINCT max(user_id)

->FROM group_message

->WHERE group_id > 1 AND group_id < 10

->GROUP BY group_id\G

***************************1. row ***************************

id:1SELECT_type: SIMPLE

table:group_message

type:range

possible_keys:idx_gid_uid_gc

key:idx_gid_uid_gc

key_len:4ref: NULL

rows:32Extra: Using WHERE; Using index; Using temporary; Usingfilesort

1row in set (0.00 sec)

最后我們再看一下這個和GROUP BY一起使用帶有聚合函數的示例,和上面第三個示例相比,可以看到已經多了filesort排序操作了,因為我們使用了MAX函數的緣故。

對於DISTINCT的優化,和GROUP BY基本上一致的思路,關鍵在於利用好索引,在無法利用索引的時候,確保盡量不要在大結果集上面進行DISTINCT操作,磁盤上面的IO操作和內存中的IO操作性能完全不是一個數量級的差距。


免責聲明!

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



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