查詢是在最開始進行的。MySQl在設計時,採用了這樣的思路:針對主要應用場景選擇一個或幾個性能優異的核心算法做爲引擎,而後努力將一些非主要應用場景做爲該算法的特例或變種植入到引擎當中。mysql
二、select實現原理sql
Join是select的核心,Join是根。性能
Join buffer:Block Nested-Loop Join (BNL)-----------join buffer 只有當join查詢中有表用到如下查詢: All:全表掃描, Index:全索引掃描, Range:範圍掃描,如 in(),id>18000, index_merge: 的時候,join查詢纔會用到join buffer。 在MySQL 中只有一種 Join 算法: Nested Loop Join(沒有其餘不少數據庫所提供的 Hash Join,也沒有 Sort Merge Join)。
【驅動表】優化
用這個表做爲第一個查詢的表,其餘表在這個基礎上遞歸。
【Nested Loop Join】ui
Nested Loop Join 就是經過驅動表的結果集做爲循環基礎數據,而後一條一條的經過該結果集中的數據做爲過濾條件到下一個表中查詢數據,而後合併結果。
若是還有第三個參與 Join,則再經過前兩個表的 Join 結果集做爲循環基礎數據,再一次經過循環查詢條件到第三個表中查詢數據,如此往復。接下來經過一個三表
join查詢來講明mysql的Nested Loop Join的實現方式。 select m.subject msg_subject, c.content msg_content from user_group g,group_message m,group_message_content c where g.user_id = 1 and m.group_id = g.group_id and c.group_msg_id = m.id 使用explain看看執行計劃
(Explain/explain extended查看執行計劃): explain select m.subject msg_subject, c.content msg_content from user_group g,group_message m, group_message_content c where g.user_id = 1 and m.group_id = g.group_id and c.group_msg_id = m.id\G; 結果以下: *************************** 1. row *************************** id: 1 select_type: SIMPLE table: g type: ref possible_keys: user_group_gid_ind,user_group_uid_ind,user_group_gid_uid_ind key: user_group_uid_ind key_len: 4 ref: const rows: 2 Extra: *************************** 2. 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: g.group_id (根據g表的group_id查,因此要先查出g表記錄,每條g表符合記錄對m表遞歸) rows: 3 Extra: *************************** 3. row *************************** id: 1 select_type: SIMPLE table: c type: ref possible_keys: idx_group_message_content_msg_id key: idx_group_message_content_msg_id key_len: 4 ref: m.id (根據m表的id查,因此要先查出m表記錄,每條m表符合條件記錄遞歸c表) rows: 2 Extra: ****************************************************** 從結果能夠看出,explain選擇user_group做爲驅動表,首先經過索引user_group_uid_ind來進行const條件的索引ref查找,而後用user_group表中過濾出來的結果集
group_id字段做爲查詢條件,對group_message循環查詢,而後再用過濾出來的結果集中的group_message的id做爲條件與group_message_content的group_msg_id
進行循環比較查詢,得到最終的結果。 這個過程能夠經過以下僞代碼來表示: for each record g_rec in table user_group that g_rec.user_id=1{ for each record m_rec in group_message that m_rec.group_id=g_rec.group_id{ for each record c_rec in group_message_content that c_rec.group_msg_id=m_rec.id pass the (g_rec.user_id, m_rec.subject, c_rec.content) row combination to output; } } 若是去掉group_message_content表上面的group_msg_id字段的索引,執行計劃會有所不同。 drop index idx_group_message_content_msg_id on group_message_content; explain select m.subject msg_subject, c.content msg_content from user_group g,group_message m, group_message_content c where g.user_id = 1 and m.group_id = g.group_id and c.group_msg_id = m.id\G; 獲得的執行計劃以下: *************************** 1. row *************************** id: 1 select_type: SIMPLE table: g type: ref possible_keys: user_group_uid_ind key: user_group_uid_ind key_len: 4 ref: const rows: 2 Extra: *************************** 2. 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: g.group_id rows: 3 Extra: *************************** 3. row *************************** id: 1 select_type: SIMPLE table: c type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 96 Extra:Using where;Using join buffer ****************************************************** 由於刪除了索引,因此group_message_content的訪問從ref變成了ALL,keys相關的信息也變成了NULL,Extra信息也變成了Using Where和Using join buffer,
這時獲取content內容只能經過對全表的數據進行where過濾才能獲取。Using join buffer是指使用到了Cache,只有當join類型爲ALL,index,range或者是index_merge
的時候纔會使用join buffer,它的使用過程能夠用下面代碼來表示: for each record g_rec in table user_group{ for each record m_rec in group_message that m_rec.group_id=g_rec.group_id{ put (g_rec, m_rec) into the join buffer if (buffer is full) flush_buffer(); } } flush_buffer(){ for each record c_rec in group_message_content that c_rec.group_msg_id = c_rec.id{ for each record in the join buffer pass (g_rec.user_id, m_rec.subject, c_rec.content) row combination to output; } empty the buffer; } 在實現過程當中能夠看到把user_group和group_message的結果集放到join buffer中,而不用每次user_group和group_message關聯後立刻和group_message_content
關聯,這也是沒有必要的;須要注意的是join buffer中只保留查詢結果中出現的列值,它的大小不依賴於表的大小,咱們在僞代碼中看到當join buffer被填滿後,mysql將會
flush buffer,flush buffer時將3者作關聯並返回到輸出output。
【針對join實現原理想到的->join語句的優化】
1、在query中用order by時,儘量利用已有的索引避免實際的排序計算;
2、在有些 query 的優化過程當中,爲了不實際的排序操做而調整索引字段的順序,甚至是增長索引字段也是值得的。固然,在調整索前,同時還須要評估調整該索引對其
他 query 所帶來的影響,平衡總體得失。
3、用小結果集驅動大結果集,儘可能減小join語句中的Nested Loop的循環總次數;
4、優先優化Nested Loop的內層循環,由於內層循環是循環中執行次數最多的,每次循環提高很小的性能都能在整個循環中提高很大的性能;
5、對被驅動表的join字段上創建索引(前面看到,沒有索引的要作全表掃描);
六、當被驅動表的join字段上沒法創建索引的時候,設置足夠的Join Buffer Size,當mysql對條件字段進行排序時,若是須要排序字段的總長度大於該參數的值的時候,
mysql就會對須要排序的字段使用臨時表進行分段,這樣也會有性能的消耗。
MySQL的select查詢中,核心功能就是JOIN查詢。
如select id, name from student; select語句在MySQL中執行時會轉換爲JOIN來處理的。
以sql:select A.id, B.score from student A left join subject B on A.id=B.id where A.age > 10 and B.score > 60;爲例
在上面的例子中,須要完成2個join:先join表A,再join表B(這裏請注意,不是涉及幾個表,就須要join幾個表,MySQL的join優化仍是挺強大的,具體解釋見後)。在MySQL進行sql解析時, 會生成一個須要join的表的list,後面會挨個對該list的表進行join操做。 繼續gdb,在sub_select函數中,能夠看到這樣一行代碼:(*join_tab->read_first_record)(join_tab)這個就是讀取表A的第一行結果,能夠看join_tab裏面的信息有表A的名字。 接下來就是很關鍵的一個函數:evaluate_join_record,這個函數主要作2件事: 1)當前已經拿到的信息進行where條件計算,判斷是否須要繼續往下走; 2)遞歸JOIN;
仍是以上面的sql爲例,首先執行第一個join,此時會遍歷表A的每一行結果,每遍歷一個結果,會進行where條件的判斷。
這裏須要注意:當前的where條件判斷只會判斷已經讀出來的列,因爲此時只讀出來表A的數據,所以如今只能對第一個where條件,即A.age > 10進行判斷,若是知足,
則遞歸調用join:sql_select.cc: 11037 rc=(*join_tab->next_select)(join, join_tab+1, 0);,這裏的next_select函數就是sub_select,MySQL就是這
樣來實現遞歸操做的。若是不知足,則不會遞歸join,而是繼續到下一行數據,從而達到剪枝的目的。 繼續跟下去,此時經過上面的next_select遞歸的又調用到sub_select上,一樣會走上面的邏輯,即先read_first_record,而後evaluate_join_record,這裏因爲表A和表
B的數據都有了,因而能夠對上面後面2個where條件:A.id = B.id和B.score > 60進行判斷了。到此,全部的where條件都已經判斷完畢,若是當前行對3個where條件都知足,就可
以將結果輸出。 以上就是select實現的大致過程,主要有2點,一個是join是採用遞歸實現的,另外一個是每讀一個表的數據,會將當前的where條件進行計算,剪枝。還有一個細節沒有提到:MySQL
是如何進行where條件判斷的?或者說,MySQL是如何進行表達式計算的? 答案就是前面提到的item類。當MySQL在解析時,會將sql解析爲不少item,同時也會創建各個item之間的關係。對於表達式,會生成一棵語法樹。好比表達式:B.score > 60,
此時會生成3個item:B.score、>和60,其中B.score和60分別是>的左右孩子,這樣,求表達式的值時,就是求>的val_int(),而後就會遞歸的調用左右子樹的val_int(),再作比
較判斷便可。 還有一個問題:如何求B.score的val_int()?對於此問題的答案我沒有具體看過,根據以前一個同事的sql實現方式,我是這樣推測的:B.score是數據表中的真實值,所以它的值
確定是經過去表中獲取。在item類中,有一個函數:fix_field,它是用於告訴外界,去哪裏獲取此item的值,每每在sql執行的預處理階段調用。因而在預處理時,告訴該item去某個
固定buffer讀取結果,同時,每當從表中讀出一行數據時,將該數據保存在該buffer中,這樣就能夠將二者關聯起來。這個部分純屬我的推測,感興趣的同窗能夠本身根據源碼看看。 再回到以前提到的一點,若是咱們將sql稍微改一下:select A.id, B.score from student A left join subject B on A.id=B.id where B.score > 60;,即去掉
第一個where條件,此時會發生什麼? 答案是,MySQL會作一個優化,將sql轉換爲select B.id, B.score from subject B where B.score > 60,這樣就不須要A同B join的邏輯了。實際上最開始我在gdb時
就用的這條sql,結果死活看不到遞歸調用sub_select的場景,還覺得原理不對,後來才發現是MySQL優化搗的亂。
【type=const】
被稱爲「常量」 驅動表或被驅動表,惟一索引的等值查詢。 在整個查詢過程當中這個表最多隻會有一條匹配的行,好比主鍵 id=1 就確定只有一行,只需讀取一次表數據便能取得所需的結果,且表數據在分解執行計劃時讀取。
【type=index_merge】
index merge-索引合併優化。 MySQL 5.0 版本以前,每一個表在查詢時 只能使用一個索引,有些不知道此功能限制的開發老是在一個表上建立不少單獨列的索引,以便當where條件中含有這些列是可以走上索引。
索引可以提供查詢速度,可是也能給平常維護和IUD 操做帶來維護成本。 MySQL 5.0 和以後的版本推出了一個新特性---索引合併優化(Index merge optimization),它讓MySQL能夠在查詢中對一個表使用多個索引,對它們同時掃描,而且合併結
果。 http://blog.itpub.net/22664653/viewspace-774687 通常在多條件過濾時, 過濾的多個列上有單獨的索引。 select * from xxx where a>xxx and c >xxxx; 當a, c列上都有索引,查詢兩個索引都走,而後要作index merge。 通常會調整爲組合索引來避免merge.
【type=index】
索引全掃描。Index與all的區別就是index只遍歷索引,不回表。 建表: create table a(a_id int not null, key(a_id)); insert into a value(1),(2) mysql> explain select * from a\G ... type: index
【type=ref】
驅動表或被驅動表,非惟一性索引訪問。
【type=eq_ref】
被驅動表,使用惟一性索引查找(主鍵或惟一性索引)。
【type=range】
以範圍的形式掃描索引,常見between,<,>,in查詢。
【type=all】
驅動表或被驅動表,全表掃描。
真正的排序是在查詢以後的。排序查找本質上仍是查找(select),只是select完成後便可獲得有序結果的屬於第一種;select根據條件查找出的結果不符合order by條件的順序時,還須要進一步排序的屬於第二種,只有第二種纔用到sort buffer。因此這部分要結合select實現原理考慮其執行計劃。在 MySQL 中的ORDER BY有兩種排序實現方式。
<第一種> 是利用有序索引獲取有序數據,這種直接從數據頁取出來的數據就是有序的,這是全部order by中最優的排序方式了,不進行實際的排序,利用了索引組織結構的有序性,並不用
sort buffer進行專門排序。 這一類排序,query的order by條件和query的執行計劃中所利用的index索引建徹底一致,且索引訪問方式爲 rang、 ref 或者 index 的時候,MySQL 能夠利用索引順序
而直接取得已經有序的數據。 <第二種> 對於排序語句,若是不能直接從索引中得到結果,那麼就要用到專門的排序算法,將取得的數據在sort buffer內存中進行排序。MySQL Query Optimizer 所給出的執行計劃
(經過 EXPLAIN 命令查看)中被稱爲 filesort。
【哪些狀況會不用專門排序---即不用sort buffer】
1、查詢條件字段和排序字段不一致。 (1)查詢條件字段中沒有索引,排序字段是主鍵。(InnoDB) Age字段上沒有加索引,id爲主鍵,會根據主鍵索引結構直接查找,其實仍是作了全表掃描邊查找邊過濾出符合age條件的記錄集。這裏作全表掃描用到primary key,是由於數據頁 原本就是由主鍵索引組織而成的,id原本就按照數據的前後順序有序.主鍵的全索引掃描,此例爲特殊的全表掃描。【若是查詢條件上沒有索引,非作全表掃描不可,除非有limit】。 二、查詢條件字段和排序字段一致,且有索引。(InnoDB/MyISAM)
【哪些狀況會用sort buffer】
查詢條件字段和排序字段一致: (1)沒有索引的字段排序:(InnoDB/MyISAM) Age字段上沒有任何索引,先全表掃描符合條件的按數據頁前後順序都查出,而後在sort buffer中排序,返回。 【若是age字段上有二級索引,則會在二級索引上直接查找到有序主鍵,根據主鍵查找到數據列表,自己就是有序的,不須要sort buffer再排序】 查詢條件字段和排序字段不一致排序: (1)查詢條件沒有索引,排序字段是主鍵。(MyISAM) MyISAM中主鍵索引和二級索引結構是如出一轍的,數據頁並非在主鍵索引結構中組織的,因此不一樣於InnoDB,這類全表掃描完成後並非按照主鍵有序的,查找結果完成後,
還須要在sort buffer中作排序即filesort。 (2)查詢條件是二級索引,排序字段是主鍵(InnoDB/MyISAM) Age字段有二級索引,按二級索引順序查找,而後在sort buffer中按主鍵ID排序,返回。【不可能按照主鍵查找,由於按照二級索引查找更快,可能就一兩條數據,按照id查找的話,
要整個聚簇索引都過一遍。查詢條件有索引和排序字段有索引時,優先按照條件字段查找】 (3)查詢條件是主鍵,排序字段有二級索引。(InnoDB/MyISAM) Id是主鍵,age字段上有二級索引。先根據主鍵的聚簇索引結構查找結果集,而後再根據age字段在sort buffer中排序,age字段的二級索引用不上。 (4)查詢條件channelId字段沒有索引,排序字段age有二級索引。(InnoDB/MyISAM) 查詢條件channelId字段沒有索引,作全表掃描,查出符合條件的結果集,而後根據age字段值在sort buffer中排序,age字段上的二級索引根本無用武之地。
【若是查詢條件上沒有索引,非作全表掃描不可,除非有limit】。【索引主要做用於查詢,對排序幾乎用不上,除非排序字段就是查詢條件,這樣沾查詢字段的光,查詢結果直接有序,
就不用再排序了】
【sort buffer所佔內存空間】
Sort buffer所使用的內存區域也就是咱們經過 sort_buffer_size 系統變量所設置的排序區。這個排序區是每一個 Thread 獨享的,因此說可能在同一時刻在 MySQL 中可能
存在多個 sort buffer 內存區域。filesort排序,若是 sort_buffer 和 read_rnd_buffer 不夠大,排序會用到磁盤文件,每部分在sort buffer中排序後,將各部門合併
成結果集。用到磁盤文件時,explain執行計劃可能除了filesort還出現Filesort_on_disk。 <1> sort_buffer_size 排序緩衝空間大小。每一個排序線程分配的緩衝區的大小。對於比較小的記錄集直接在內存的sort buffer中進行排序便可完成,若是空間不夠大可能用到磁盤文件,因此適當加大sort_b
uffer_size,使排序只在內存中進行,能夠加快ORDER BY或GROUP BY操做。 <2>Sort_merge_passes 若是sort_buffer_size不夠大,用到磁盤文件排序,大排序分紅多個子排序,每部分在sort buffer中排序後,將各部門合併成結果集。有多少個子排序這個值就是多少。這個變量也
是thread級別的。
【sort buffer排序算法實現】
MySQL中filesort 的實現算法其實是有兩種的: 一種:首先根據相應的條件取出相應的排序字段和能夠直接定位行數據的行指針信息,而後在 sort buffer 中對條件進行排序排好序以後利用指針取出數據行中的請求數據,而後返回
給客戶端。之前老版本只支持這種,以後版本也都支持; 一種:第一種算法基礎上優化後的算法。Mysql4.1之後開始支持。一次性取出排序的條件字段和其餘全部請求的字段,將不用於排序的字段存放在一塊內存中,而後在 sort buffer 中
對條件字段進行排序,排好序後利用行指針將在內存中的數據進行匹配合並結果集,而後將排好序的數據返回給客戶端。減小第一次算法中須要兩次訪問表數據的 IO 操做,將兩次變成了
一次,但相應也會耗用更多的 sort buffer 空間。 <3>max_length_for_sort_data MySQL 主要經過比較咱們所設定的系統參數 max_length_for_sort_data 的大小和 Query 語句所取出的字段類型大小總和來斷定須要使用哪種排序算法。若是 max_length_fo
r_sort_data 更大,則使用第二種優化後的算法,反之使用第一種算法。因此若是但願 ORDER BY 操做的效率儘量的高,必定要注意 max_length_for_sort_data 參數的設置。
曾經就有同事的數據庫出現大量的排序等待,形成系統負載很高,並且響應時間變得很長,最後查出正是由於 MySQL 使用了傳統的第一種排序算法而致使,在加大了 max_length_for_
sort_data 參數值以後,系統負載立刻獲得了大的緩解,響應也快了不少。
【大字段排序】
<4>max_sort_length
默認1024.當排序BLOB或TEXT值時使用的字節數。只使用每一個值的前max_sort_length字節;其它的被忽略。
【sort buffer】
MySQL 用此內存區域進行排序操做(filesort),完成客戶端的排序請求。當咱們設置的排序區緩存大小沒法知足排序實際所需內存的時候,MySQL 會將數據寫入磁盤文件來完成排序。
因爲磁盤和內存的讀寫性能徹底不在一個數量級,因此sort_buffer_size參數對排序操做的性能影響絕對不可小視。
Sort buffer在server層,跟存儲引擎沒有關係,若是從存儲引擎返回的數據有序則不排序,不然在sort buffer中排序。
【order by優化】
1.加大max_length_for_sort_data參數值,儘可能使mysql使用第二種排序算法,這樣能夠減小大量的IO操做。當須要取出的全部數據長度小於這個參數的值的時候,mysql將採用第
二中改進的排序算法,不然,使用第一種算法,因此只要內存充足就能夠設置足夠大的值來讓mysql採用改進的排序算法; 2. 去掉沒必要要的返回字段,很容易從上一點知道緣由; 3. 增大sort_buffer_size參數的值,當mysql對條件字段進行排序時,若是須要排序字段的總長度大於該參數的值的時候,mysql就會對須要排序的字段使用臨時表進行分段,這樣也
會有性能的消耗。 4.加大max_length_for_sort_data參數的設置。當須要取出的全部數據長度小於這個參數的值的時候,mysql將採用第二中改進的排序算法。 同時,去掉沒必要要的返回字段,這能夠
減小數據長度大於max_length_for_sort_data的可能;
分組是在排序以後的。
GROUP BY 實際上也一樣會進行排序操做,並且與 ORDER BY 相比,GROUP BY 主要只是多了排序以後的分組操做。固然,若是在分組的時候還使用了其餘的一些聚合函數,那麼還須要一些聚合函數的計算。因此,在GROUP BY 的實現過程當中,與 ORDER BY 同樣也能夠利用到索引。
SELECT * FROM table1 group by channelId; mysql> SELECT id,groupId,userId,channelId FROM table1 group by channelId; +----+---------+--------+-----------+ | id | groupId | userId | channelId | +----+---------+--------+-----------+ | 2 | 3 | 12 | 1 | | 20 | 2 | 10 | 2 | | 30 | 3 | 23 | 3 | | 3 | 4 | 3 | 12 | | 4 | 4 | 3 | 13 | +----+---------+--------+-----------+ mysql> explain SELECT id,groupId,userId,channelId FROM table1 group by channelId; +----+-------------+--------+-------+---------------+--------+---------+------+------+----------------- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra +----+-------------+--------+-------+---------------+--------+---------+------+------+----------------- | 1 | SIMPLE | table1 | index | NULL | index1 | 12 | NULL | 5 | Using index; Using temporary; Using filesort | +----+-------------+--------+-------+---------------+--------+---------+------+------+----------------- 在 MySQL 中,GROUP BY 的實現一樣有多種(三種)方式,其中有兩種方式會利用現有的索引信息來完成 GROUP BY,另一種爲徹底沒法使用索引的場景下使用。下面咱們分別針對
這三種實現方式作一個分析。
【鬆散(Loose)索引掃描實現 GROUP BY】 所謂的鬆散索引掃描,就是mysql不須要掃描全部知足條件的索引鍵便可完成group by操做,下面經過一個簡單的實例來分析一下這個過程。 CREATE TABLE `table1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(4000) DEFAULT NULL, `groupId` int(11) NOT NULL, `userId` int(11) NOT NULL, `channelId` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `index1` (`groupId`,`userId`,`channelId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 【由於數據太少的話全部數據都在一個數據頁,查找直接就查數據頁僅有的一頁去了,不能表明典型,因此這裏用name字段4000個單位varchar來充量,插入5條記錄,
造成一個小型的索引結構,數據以下圖】
Mysql>SELECT userId,max(channelId) FROM table1 where groupId>1 group by groupId,userId ;
理解: group by的特性:有多少個goupId和userId組合就有多少group組, 由於索引自己是按goupId,userId,channelId排序的,因此檢查到id=3這個記錄對應索引<4,3,…>時不作任何操做,向下檢查檢查到又是,<4,3,…>,不作任何操做,檢查下一個
索引變成好比<5,1,..>跟<4,3,…>不同了,將最後一個<4,3,…>對應的channelId記錄到結果集便可,不用每一個<4,3,…>都將channelId取出來作比較。 Mysql> explain SELECT groupId ,max(channelId) FROM table1 where groupId<10 group by groupId,userId;
執行計劃的 Extra中顯示「Using index for group-by」,告訴咱們,MySQL Query Optimizer 經過使用鬆散索引掃描來實現了GROUP BY 操做。 要利用到鬆散索引掃描實現 GROUP BY,須要至少知足如下幾個條件: ◆GROUP BY 條件字段必須在同一個索引中最前面的連續位置; ◆在使用GROUP BY 的同時,只能使用 MAX 和 MIN 這兩個聚合函數(由於利用的是索引自身的有序順序,要不就最前要不最後); ◆若是引用到了該索引中 GROUP BY 條件以外的字段條件的時候,必須以常量形式存在; 爲何鬆散索引掃描的效率會很高? 由於在沒有WHERE子句,也就是必須通過全索引掃描的時候, 鬆散索引掃描須要讀取的鍵值數量與分組的組數量同樣多,也就是說比實際存在的鍵值數目要少不少。而在WHERE子句包含範圍判斷式或者等值表達式的時候, 鬆散索引掃描查找知足範圍條件的每一個組的第1個關鍵字,而且再次讀取儘量最少數量的關鍵字。 【使用緊湊(Tight)索引掃描實現 GROUP BY】 緊湊索引掃描實現 GROUP BY 和鬆散索引掃描的區別主要在於他須要在掃描索引的時候,讀取全部知足條件的索引鍵,而後再根據讀取惡的數據來完成 GROUP BY 操做獲得相應結果。 mysql> explain select userId,max(channelId) FROM table1 where groupId=2 group by userId; +----+-------------+--------+------+---------------+--------+---------+-------+------+----------------- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra +----+-------------+--------+------+---------------+--------+---------+-------+------+----------------- | 1 | SIMPLE | table1 | ref | index1 | index1 | 4 | const | 2 | Using where; Using index | 這時GROUP BY 操做也是經過索引完成的,只不過是須要訪問WHERE條件groupId=4的所有索引鍵信息以後才能得出結果。
【使用臨時表實現 GROUP BY】
鬆散索引掃描
MySQL 在進行 GROUP BY 操做的時候要想利用全部,必須知足 GROUP BY 的字段必須同時存放於同一個索引中,且該索引是一個有序索引(如 Hash 索引就不能知足要求)。並且,並不僅是如此,是否可以利用索引來實現 GROUP BY 還與使用的聚合函數也有關係。當 MySQL Query Optimizer 沒法找到合適的索引能夠利用的時候,就不得不先讀取須要的數據,而後經過臨時表來完成 GROUP BY 操做。
mysql> explain select userId,max(channelId) FROM table1 where groupId>2 group by userId;
+----+-------------+--------+-------+---------------+--------+---------+------+------+-----------------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
+----+-------------+--------+-------+---------------+--------+---------+------+------+-----------------| 1 | SIMPLE | table1 | range | index1 | index1 | 4 | NULL | 4 | Using where; Using index; Using temporary; Using filesort |
此次的執行計劃很是明顯的告訴咱們 MySQL 經過索引找到了咱們須要的數據,而後建立了臨時表,又進行了排序操做,才獲得咱們須要的 GROUP BY 結果。
distinct是在分組以後的。
DISTINCT 實際上和 GROUP BY 操做的實現很是類似,只不過是在 GROUP BY 以後的每組中只取出一條記錄而已。因此,DISTINCT 的實現和 GROUP BY 的實現也基本差很少,
沒有太大的區別。一樣能夠經過鬆散索引掃描或者是緊湊索引掃描來實現,固然,在沒法僅僅使用索引即能完成 DISTINCT 的時候,MySQL 只能經過臨時表來完成。可是,和 GROUP
BY 有一點差異的是,DISTINCT並不須要進行排序。也就是說,在僅僅只是 DISTINCT 操做的 Query 若是沒法僅僅利用索引完成操做的時候,MySQL 會利用臨時表來作一次數據的
「緩存」,可是不會對臨時表中的數據進行 filesort 操做。 1.經過鬆散索引掃描完成 DISTINCT : sky@localhost : example 11:03:41> EXPLAIN SELECT DISTINCT group_id -> FROM group_messageG *************************** 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 1 row in set (0.00 sec) 執行計劃中的 Extra 信息爲「Using index for group-by」,這表明什麼意思?爲何我沒有進行 GROUP BY 操做的時候,執行計劃中會告訴我這裏經過索引進行了 GROUP BY 呢?
其實這就是於 DISTINCT 的實現原理相關的,在實現 DISTINCT的過程當中,一樣也是須要分組的,而後再從每組數據中取出一條返回給客戶端。而這裏的 Extra 信息就告訴咱們,MySQ
L 利用鬆散索引掃描就完成了整個操做。
2.經過緊湊索引掃描完成distinct: sky@localhost : example 11:03:53> EXPLAIN SELECT DISTINCT user_id -> FROM group_message -> WHERE group_id = 2G *************************** 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 1 row 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 < 10G *************************** 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 1 row 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_idG *************************** 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 1 row in set (0.00 sec) 最後咱們再看一下這個和 GROUP BY 一塊兒使用帶有聚合函數的示例,和上面第三個示例相比,能夠看到已經多了 filesort 排序操做了,正是由於咱們使用了 MAX 函數的緣故。要取得分組後的 MAX 值,又沒法使用索引完成操做,只能經過排序才行了。