Mysql優化之 小表驅動大表

Mysql在進行關聯表查詢時,通常先對一張表A進行查詢,然後將循環使用查詢出來的每一條記錄的關聯信息,再來查詢另一張表B中的記錄,這樣查詢次數爲1 + COUNT(從表A查詢的記錄)。所以,爲了提高查詢效率,需要儘量將小表作爲首先查詢的表,我們可以關聯查詢時首次使用的表A爲主表,將其它表如表B成爲嵌套表。

一、IN和EXISTS查詢

根據MySQL高級知識(十)——批量插入數據腳本中的相應步驟在tb_dept_bigdata表中插入100條數據,在tb_emp_bigdata表中插入5000條數據。

注:100個部門,5000個員工。tb_dept_bigdata(小表),tb_emp_bigdata(大表)。

①當B表的數據集小於A表數據集時,用in由於exists。

select *from tb_emp_bigdata A where A.deptno in (select B.deptno from tb_dept_bigdata B)

B表爲tb_dept_bigdata:100條數據,A表tb_emp_bigdata:5000條數據。

用in的查詢時間爲:

將上面sql轉換成exists:

select *from tb_emp_bigdata A where exists(select 1 from tb_dept_bigdata B where B.deptno=A.deptno);

用exists的查詢時間:

經對比可看到,在B表數據集小於A表的時候,用in要由於exists,當前的數據集並不大,所以查詢時間相差並不多。

②當A表的數據集小於B表的數據集時,用exists由於in。

select *from tb_dept_bigdata A where A.deptno in(select B.deptno from tb_emp_bigdata B);

用in的查詢時間爲:

將上面sql轉換成exists:

select *from tb_dept_bigdata A where exists(select 1 from tb_emp_bigdata B where B.deptno=A.deptno);

用exists的查詢時間:

由於數據量並不是很大,因此對比並不是難麼的強烈。

附上視頻的結論截圖:

 

結論:

in後面跟的是小表,exists後面跟的是大表。

簡記:in+小表,exists+大表。

對於exists

select .....from table where exists(subquery);

可以理解爲:先進行subquery,如果存在,再進行select .....from table的查詢。

 

二、JOIN查詢

A JOIN B,表A是左表,表B示右表;左表永遠是主表,右表永遠是嵌套表。

Mysql在版本5.5之前,使用了Nest Loop Join算法,在版本5.5之後,採用了優化的Block Nested-Loop Join算法。優化的Block Nested-Loop Join方法,首先將外層循環數據(即主表查詢出來的數據)的一部分緩存在join buffer(join_buffer_size決定這個緩衝區的大小)中,然後讀取嵌套表數據,逐條和buffer中的數據進行對比,從而減少了查表次數,這樣便可以提高效率。
Block Nested-Loop Join算法: https://dev.mysql.com/doc/refman/5.7/en/nested-loop-joins.html#block-nested-loop-join-algorithm

建立 t1,t2 兩個完全相同的表,t1 表中寫入 100 條數據,t2 表中 寫入 1000 條數據。

Index Nested-Loop Join(NLJ) (被驅動表有索引的情況選擇)

  - 語句

    - 爲了避免Mysql選擇驅動表對於分析的影響,改用 straight_join 讓 MySQL 使用固定的連接方式執行查詢。

    - t1 是驅動表,t2 是被驅動表。

    - select * from t1 straight_join t2 on (t1.a=t2.a);

 

  - 執行流程

    - 在這條語句裏,被驅動表 t2 的字段 a 上有索引,join 過程用上了這個索引

    - 從表 t1 中讀入一行數據 R;

    - 從數據行 R 中,取出 a 字段到表 t2 裏去查找;

    - 取出表 t2 中滿足條件的行,跟 R 組成一行,作爲結果集的一部分;

    - 重複執行步驟 1 到 3,直到表 t1 的末尾循環結束。 

    - 

 

  - 小結

    - 這個過程是先遍歷表 t1,然後根據從表 t1 中取出的每行數據中的 a 值,去表 t2 中查找滿足條件的記錄。

    - 在形式上,這個過程很像寫程序時的嵌套查詢類似,並且可以用上被驅動表的索引,所以我們稱之爲“Index Nested-Loop Join”,簡稱 NLJ。 

    - 整個過程, 總掃描行數是 200(t1 100 + t2 索引樹100)

 

Block Nested-Loop Join(NLJ)(被驅動表無索引選擇)

  - 語句

    -  select * from t1 straight_join t2 on (t1.a=t2.b);

    - 由於表 t2 的字段 b 上沒有索引,因此在執行流程時,每次到 t2 去匹配的時候,就要做一次全表掃描。

 

  - 流程

    - 把表 t1 的數據讀入線程內存 join_buffer 中,由於我們這個語句中寫的是 select *,因此是把整個表 t1 放入了內存;

    - 掃描表 t2,把表 t2 中的每一行取出來,跟 join_buffer 中的數據做對比,滿足 join 條件的,作爲結果集的一部分返回。

    - 

 

  - 小結

    - 可以看到,在這個過程中,對錶 t1 和 t2 都做了一次全表掃描,因此總的掃描行數是 100100。

    - 由於 join_buffer 是以無序數組的方式組織的,因此對錶 t2 中的每一行,都要做 100 次判斷,總共需要在內存中做的判斷次數是:100*1000=10 萬次。

    - join_buffer 的大小是由參數 join_buffer_size 設定的,默認值是 256k。如果放不下表 t1 的所有數據話,策略很簡單,就是分段放。

 

五:總結

  - 能不能使用 join ?

    - 如果可以使用 Index Nested-Loop Join 算法,也就是說可以用上被驅動表上的索引,其實是沒問題的;

    - 如果使用 Block Nested-Loop Join 算法,掃描行數就會過多。

      - 尤其是在大表上的 join 操作,這樣可能要掃描被驅動表很多次,會佔用大量的系統資源。所以這種 join 儘量不要用。

 

  - 如果要使用 join,應該選擇大表做驅動表還是選擇小表做驅動表?

    - 本人感覺沒有區別,在考慮Index Nested-Loop Join 算法的之外,唯一有區別的就是LEFT JOIN、RIGHT JOIN和INNER JOIN的語義。建議在考慮Index Nested-Loop Join 算法的之外,儘量少用JOIN語法。

               - 如果非要選擇join,可以考慮將被驅動表儘量小如應用子表,也是一種優化方法。因爲驅動表可以使用where縮小範圍,而被驅動表只能通過應用子表縮小範圍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章