小表驅動大表
1、概念
驅動表的概念是指多表關聯查詢時,第一個被處理的表,使用此表的記錄去關聯其他表。驅動表的確定很關鍵,會直接影響多表連接的關聯順序,也決定了后續關聯時的查詢性能。
2、原則
驅動表的選擇遵循一個原則:
在對最終結果集沒影響的前提下,優先選擇結果集最小的那張表作為驅動表。改變驅動表就意味着改變連接順序,只有在不會改變最終輸出結果的前提下才可以對驅動表做優化選擇。外連接的順序改變就很可能影響結果。
預估結果集的原則:
- 如果where里沒有相應表的篩選條件,無論on里是否有相關條件,默認為全表
- 如果where里有篩選條件,但是不能使用索引來篩選,那么默認為全表
- 如果where里有篩選條件,而且可以使用索引,那么會根據索引來預估返回的記錄行數
3、識別
- explain顯示結果里排在第一行的就是驅動表
4、嵌套循環算法
(1) 4種算法
- 在使用索引關聯的情況下,有Index Nested-Loop join和Batched Key Access join兩種算法;
- 在未使用索引關聯的情況下,有Simple Nested-Loop join和Block Nested-Loop join兩種算法;
(2) Nested-Loop Join Algorithms
一個簡單的嵌套循環聯接(NLJ)算法,循環從第一個表中依次讀取行,取到每行再到聯接的下一個表中循環匹配。這個過程會重復多次直到剩余的表都被聯接了。通過外循環的行去匹配內循環的行,所以內循環的表會被掃描多次。
for each row in t1 matching range {
for each row in t2 matching reference key {
for each row in t3 {
if row satisfies join conditions,
send to client
}
}
}
(3) Block Nested-Loop Join Algorithm
一個塊嵌套循環聯接(BNL)算法,將外循環的行緩存起來,讀取緩存中的行,減少內循環的表被掃描的次數。
MySQL使用聯接緩沖區時,會遵循下面這些原則:
-
join_buffer_size系統變量的值決定了每個聯接緩沖區的大小;
-
聯接類型為ALL、index、range時(換句話說,聯接的過程會掃描索引或數據時),MySQL會使用聯接緩沖區;
-
緩沖區是分配給每一個能被緩沖的聯接,所以一個查詢可能會使用多個聯接緩沖區;
-
聯接緩沖區永遠不會分配給第一個表,即使該表的查詢類型為ALL或index;
-
聯接緩沖區聯接之前分配,查詢完成之后釋放;
-
使用到的列才會放到聯接緩沖區中,並不是所有的列;
-
每個join關鍵字就對應着一個join buffer,也就是驅動表和第二張表用一個join buffer,得到的塊結果集與第三章表用一個join buffer
設S是每次存儲t1、t2組合的大小,C是組合的數量,則t3被掃描的次數為:
(S * C)/join_buffer_size + 1
(4) Index Nested-Loop join
通過索引關聯被驅動表,使用的是Index Nested-Loop join算法,不會使用msyql的join buffer。根據驅動表的篩選條件逐條地和被驅動表的索引做關聯,每匹配到符合的記錄,放入net-buffer中,然后繼續關聯,直到net-buffer滿了,返回給client,清空net-buffer,此緩存區由net_buffer_length參數控制
(5) Batched Key Access join
原理:
1、逐條的根據where條件查詢驅動表,將符合記錄的數據行放入join buffer,然后根據關聯的索引獲取被驅動表的索引記錄,存入read_rnd_buffer。join buffer和read_rnd_buffer都有大小限制,無論哪個到達上限都會停止此批次的數據處理,等處理完清空數據再執行下一批次。也就是驅動表符合條件的數據可能不能夠一次處理完,而要分批次處理。
2、當達到批次上限后,對read_rnd_buffer里的被驅動表的索引按主鍵做遞增排序,這樣在回表查詢時就能夠做到近似順序查詢
3、因為mysql的InnoDB引擎的數據是按聚集索引來排列的,當對非聚集索引按照主鍵來排序后,再用主鍵去查詢就使得隨機查詢變為順序查詢,而計算機的順序查詢有預讀機制,在讀取一頁數據時,會向后額外多讀取最多1M數據。此時順序讀取就能排上用場。
以下示例均以此為基礎數據:
create table a(a1 int primary key, a2 int ,index(a2)); --雙字段都有索引
create table c(c1 int primary key, c2 int ,index(c2), c3 int); --雙字段都有索引
create table b(b1 int primary key, b2 int); --有主鍵索引
create table d(d1 int, d2 int); --沒有索引
insert into a values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
insert into b values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
insert into c values(1,1,1),(2,4,4),(3,6,6),(4,5,5),(5,3,3),(6,3,3),(7,2,2),(8,8,8),(9,5,5),(10,3,3);
insert into d values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
sql如下:
select a.*,c.* from a join c on a.a2=c.c2 where a.a1>4;(下圖應為a1>4)
使用:
BKA算法在需要對被驅動表回表的情況下能夠優化執行邏輯,如果不需要會表,那么自然不需要BKA算法
如果要使用 BKA 優化算法的話,你需要在執行 SQL 語句之前先設置:
set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
前兩個參數的作用是要啟用 MRR(Multi-Range Read)。這么做的原因是,BKA 算法的優化需要依賴於MRR,官方文檔的說法,是現在的優化器策略,判斷消耗的時候,會更傾向於不使用 MRR,把 mrr_cost_based 設置為 off,就是固定使用 MRR 了。)
(6)嵌套循環的執行過程
多表連接如何執行?是先兩表連接的結果集然后關聯第三張表,還是一條記錄貫穿全局?
sql如下:
select a.*,b.*,c.* from a join c on a.a2=c.c2 join b on c.c2=b.b2 where b.b1>4;