join算法分析

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

多個條件,加複合索引算法

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

上面都是比較基本的,這篇咱們分析一些複雜的狀況——join的算法數據庫

以下兩張表作join併發

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

Ⅰ、nested_loop join

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),也就是笛卡兒積oracle

1.二、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)oop

優化器傾向於使用小表作驅動表,內表上建立索引便可性能

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:
一、不知道哪一個表是驅動表,那就簡單點,兩個關聯的表的相關列都加索引,讓優化器本身選擇

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

三、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,這樣不會比在數據庫層作快

相關文章
相關標籤/搜索