join算法分析


對於單條語句,explain看下key,加個索引

多個條件,加復合索引

where a = ? order by b 加(a,b)的復合索引

上面都是比較基本的,這篇我們分析一些復雜的情況——join的算法

如下兩張表做join

10w            100w
tb R             tb S
  r1               s1
  r2               s2
  r3               s3
  ...               ...
  rN              sN   

Ⅰ、nested_loop join

1.1、simple nested_loop join 這個在數據庫中永遠不會使用

For each row r in R do
    For each row s in S do
        If r and s satisfy the join condition
            Then output the tuple <r,s>


掃描成本 O(Rn*Sn),也就是笛卡兒積

1.2、index nested_loop join 最推薦

For each row r in R do
    lookup in S index
        if found s == row
            Then output the tuple<r,s>

掃描成本 O(Rn)

優化器傾向於使用小表做驅動表,內表上創建索引即可

select * from a,b where a.x=b.y

a中的每條記錄,去b上面的index(y)中去找這個x的記錄,a表和b表,哪個是R,哪個是S

R:驅動表(外表) S:內表

對於inner join,a b 和 b a join出來結果一樣,用的索引來查詢,100w和1000w掃描成本都一樣,都是外表的行數,所以只要驅動表越小,優化器就傾向於用它做驅動表

對於left join,左表要全表掃描所有記錄,所以一定是驅動表

比如,一列10w行,另一列100w行,優化器會喜歡把10w的這一列做外表,然后在100w的列上建索引,外表只要掃描10w次,假設100w記錄索引B+ tree高度是3,那一共就是10w * 3次io操作,反過來,就是100w * 3次,所以,mysql只要兩張表關聯,非常建議兩張表上一定要有索引,索引應該創建在S表上,也就是大表,如果索引加反了,這時候100w的表就成了驅動表

但是線上肯定不會直接兩張表關聯,where后面還有很多過濾條件,優化器會把這些條件考慮進去,過濾掉這些條件,看哪張表示小表就是驅動表,但是索引有傾斜,優化器在選擇上可能會出錯

1.3 block nested_loop join 兩張表上沒有索引的時候才會使用的算法

用來優化simle nested_loop join,減少內部表的掃描次數

For each tuple r in R do
    store used columns as p from R in join buffer
        For each tuple s in S do
            if p and s satisfy the join condition
                Then output the tuple <p,s>


加一個內存,join_buffer_size變量,空間換時間,這個變量決定了Join Buffer的大小

Join Buffer可被用於聯接是ALL,index,range的類型

Join Buffer只存儲需要進行查詢操作的相關列數據(要關聯的列),而不是整行的記錄(千萬要記住),所以總的來說還是蠻高效的,

掃描成本呢?和simple一樣

Table R join buffer Table S
將多條R中的記錄一下子放到join buffer中,join buffer 一次性和Table S中每條記錄進行比較,這樣來減少內表的掃描次數,假設join buffer足夠大,大到能把驅動表中所有記錄cache起來,那這樣就只要掃一次內表

舉例:

A    B
1    1
2    2
3    3
     4
外  內

                 SNLP     BNLP
外表掃描次數       1         1
內表掃描次數       3         1
比較次數          12        12

綜上:比較次數是節省不下來的

如果是百萬級別,mysql勉強可以跑出來結果,調優的話,不談索引,我們就要調大join_buffer_size這個參數,默認256k,如果這個列是8個字節,256k顯然存不下太多記錄,這個參數可以調很大,但是不要過分了,不然機器內存不夠,數據庫掛了就不好了,一般來說1g最大了

join_buffer_size是私有的,每個線程可以用的內存大小

網上有個特別錯誤的觀點,mysql加速join查詢,加大join_buffer_size。這個是兩張表關聯沒有索引用這個方法才有用,你有了索引,再怎么加大這個參數也沒用

本身也不太建議用這個算法,除非某些特殊的查詢用不到索引,或者當時沒建索引,這是個臨時處理方法,比較次數是笛卡兒積啊 我的天那,如何把這個比較次數降下來呢?

tips:
1、不知道哪個表是驅動表,那就簡單點,兩個關聯的表的相關列都加索引,讓優化器自己選擇

2、oracle中百萬級別的表進行關聯,用index nested_loop join,千萬級以上用hash join這個說法怎么看?心法口訣是什么

3、join如果有索引,為什么千萬級別的join不行?這句話是錯的,是可以的,只是速度很慢罷了,主要就是因為要回表

select max(l_extendedprice) from orders,lineitem
where o_orderdate betweent '1995-01-01' and '1995-01-31' and
l_orderkey=o_orderkey;

join的列不是主鍵,這種類型的查詢就是基於索引的,join不太適用的場景

看一個問題

(root@localhost) [dbt3]> explain select * from orders where o_orderdate > '1997-01-01';
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | orders | NULL       | ALL  | i_o_orderdate | NULL | NULL    | NULL | 1483643 |    50.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

(root@localhost) [dbt3]> explain select * from orders where o_orderdate > '1999-01-01';
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys | key           | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | orders | NULL       | range | i_o_orderdate | i_o_orderdate | 4       | NULL |    1 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

為什么第一條sql沒走索引第二條走了呢?

這不是優化器不穩定,這個索引是一個二級索引,現在select * 所以要回表,回表對於我們這個sql來說,假設通過索引定位到回表的記錄一共是50w條(表一共是150w條記錄,每條記錄大小是100字節),那就要回表50w次,大約是50w次io(看B+ tree 高度,這里假設高度就是1)

如果不走索引,直接掃150w行主鍵一共需要150w/(16k/100)次io(每行大小100字節,一個頁就是16k/100條記錄,150w除以這個值,就是一共的記錄數),到這里看基本上就是1w次io,這就是優化器沒走索引的原因,而且,回表用的io是隨機的,掃主鍵用的是順序io,后者比前者最起碼快十倍

另外,這個選擇肯定是基於cost的,但不要追究cost,官方沒有說明cost是怎么計算的,通過源碼可以看出一點,但是5.7版本又不一樣了

那針對這種情況我們應該如何優化呢?——MRR(5.7之后才有,和oracle一樣)

(root@localhost) [dbt3]> explain select /* +MRR(orders) */ * from orders where o_orderdate > '1998-01-01';
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+--------+----------+----------------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key           | key_len | ref  | rows   | filtered | Extra                            |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+--------+----------+----------------------------------+
|  1 | SIMPLE      | orders | NULL       | range | i_o_orderdate | i_o_orderdate | 4       | NULL | 317654 |   100.00 | Using index condition; Using MRR |
+----+-------------+--------+------------+-------+---------------+---------------+---------+------+--------+----------+----------------------------------+
1 row in set, 1 warning (0.22 sec)

MRR:Multi-Range-Read:多范圍的read,空間換時間,解決回表的性能問題

隨機io回表,不能保證key和pk都是順序的,對於二級索引來說它的key是排序了,但是不能保證里面保存的pk也排序了

所以現在弄了個內存,把這個二級索引里面的pk都放進去,等放不下了,去sort一把,然后再去回表,這時候io就比較順序了,這樣通過隨機轉順序做了個優化

Ⅱ、classic hash join

把外表中的數據放到一個內存中,放進去之后不是直接去和內表比較,對內存中的列創建了一個hash表,然后再掃描內表,內表中的每條記錄去探測hash表,通常查一個記錄只要探測一次

A    B
1    1
2    2
3    3
     4

外表掃1次,內表掃1次,比較次數是4(S表去hash表中探測),走索引的話也是1 1 4,看起來哈希和索引看起來成本一樣,hash不用創建索引,不用回表

hash join 就不存在回表的問題,還可以用來做並發,如果join要回表,用基於索引的方式做join算法效率比較差,對於oracle的話,hash join就會好非常多

hash join有個缺點是只支持等值的查詢,A.x=B.y >= 這種就歇菜了,不過通常join也是等值查詢

聽說8.0會支持

Ⅲ、batched key access join(5.6開始支持)

用來解決如果關聯的列是二級索引,對要回表的列先cache起來,排序,再回表,性能會比較好一點

bka join調的是mrr接口,但是現在這個算法有bug,默認是永遠不啟動的,要啟用得寫hint,這是mysql現在比較大的問題

上面講了一堆mysql的不是,join算法的痤,上面這些多少w行join,線上業務會用到這些嗎?我們線上都是簡單查詢,這些都是很復雜的查詢了,233333

錯誤做法:有人說業務層做join,這樣不會比在數據庫層做快


免責聲明!

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



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