join語句的兩種算法,分別是:NLJ和BNL
測試數據:
create table t1(id int primary key, a int, b int, index(a)); create table t2 like t1; drop procedure idata; delimiter ;; create procedure idata() begin declare i int; set i=1; while(i<=1000)do insert into t1 values(i, 1001-i, i); set i=i+1; end while; set i=1; while(i<=1000000)do insert into t2 values(i, i, i); set i=i+1; end while; end;; delimiter ; call idata();
Multi-Range Read優化
MRR的設計思路就是:大多數數據都是按照主鍵遞增的順序插入得到的,所以我們可以認為,如果按照主鍵的遞增順序查詢的話,多磁盤的讀比較接近於順序讀,能夠提升讀的性能。
此時,執行語句將會變成這樣:
- 根據索引a,定位滿足條件的記錄,將id值放入read_rnd_buffer中;
- 將read_rnd_buffer中的id進行遞增排序
- 排序后的id數組,依次到主鍵 id索引中查找記錄,並作為結果返回。
如果我們想穩定低使用MRR優化的話,需要設置set optimizer_switch="mrr_cost_based =off"參數
總結一下:MRR的核心就是,這條查詢語句在索引a上做的是一個范圍查詢(多值查詢),可以得到足夠多的主鍵id,這樣通過排序后,再去主鍵索引差數據,才能體現出“順序性”
Batched key Access優化
MySQL在5.6版本后,引入了BKA算法,其實就是對NLJ算法的優化
NLJ的邏輯是,從驅動表t1,逐行取出a的值,再到驅動表t2去做join。
我們可以把表t1 的數據取出來一部分,先放到一個臨時內存中,這個臨時內存就是join_buffer
啟動BKA算法的參數是:
set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
BNL性能優化
在通常情況下,我們可以直接在被驅動表上建立索引,這樣就可以將BNL算法成NLJ算法了。但是有一些被驅動表不適合做索引,例如:
select * from t1 join t2 on (t1.b=t2.b) where t2.b>=1 and t2.b<=2000;
這句SQL表示,在經過where條件過濾,需要參與join的只有2000行數據。如果這條數據是一個低頻的SQL,那么在為表t2建立一個字段b的索引,就很浪費了。
但是如果我們使用BNL算法,那么我們首先會將表t1存入join_buffer,然后掃描表t2與join_buffer中的數據進行比對,這里的數據是指所有的數據。這個判斷等值條件的次數是1000*100萬=10億次。

這個時候,我們可以考慮使用臨時表來優化,大致的思路是:
- 把表中t2滿足條件的數據,先放在臨時表tmp_t中;
- 為了讓join可以使用BKA算法, 給臨時表tmp_t的字段加上索引
- 讓臨時表和t1做join操作
create temporary table temp_t(id int primary key, a int, b int, index(b))engine=innodb; insert into temp_t select * from t2 where b>=1 and b<=2000; select * from t1 join temp_t on (t1.b=temp_t.b);
此時,可以明顯看出,速度快了很多。
總結:
在被驅動表的字段上加索引
創建臨時表,在臨時表中加索引
hash join
我們可以自己在業務段,創建hash結構,然后將數據讀出來,自己進行數組的匹配和數組集的組裝。也可以提高join的性能。
總結:
BKA優化是MySQL內置支持的,建議使用
BNL效率低,建議轉成BKA算法
臨時表的方案,對於能提前過濾出小數據的join語句來說,效果很好
hash join的效果也是很好的,基於內存計算,路論上效果優於臨時表