mysql join的實現原理及優化思路
join的實現原理
join的實現是採用Nested Loop 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 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 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 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,rang或者是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 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 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。
join語句的優化
1. 用小結果集驅動大結果集,儘量減少join語句中的Nested Loop的迴圈總次數;
2. 優先優化Nested Loop的內層迴圈,因為內層迴圈是迴圈中執行次數最多的,每次迴圈提升很小的效能都能在整個迴圈中提升很大的效能;
3. 對被驅動表的join欄位上建立索引;
4. 當被驅動表的join欄位上無法建立索引的時候,設定足夠的Join Buffer Size。