Mysql 百問系列 : Join 會有哪幾種工做方式?

前言:

做者本人從事it 行業多年,沒進過什麼大企業。摸爬滾打多年,多少有些經驗。抗的了服務器,寫的了前端。前端

熟悉的語言:PHP,JavaScript,Python 等。mysql

熟悉的框架:Laravel,Django,Scrapy,React,Vue 等。sql

數據庫:Mysql,Redis,Es等。數據庫

早些年其實就想寫些東西,但又無處下手。直到這幾年,帶領團隊,給組員培訓,分享。纔想到爲何不把要培訓的內容,分享的知識寫成博文呢,而不是簡單幾頁PPT。bash

除了這篇 "Mysql 百問系列" 後續還會有更多系列跟你們見面吧。文中知識點都是查閱資料,親自實踐得出來的結論,若是理解有偏誤,請不吝賜教。服務器

閱讀說明:

每同樣 技術 或者 功能 都是爲了解決某一個問題而誕生的。 搞清楚 爲何 要這麼作 比 搞清楚 具體怎麼作 要重要。 因此我都會以問題開頭,但願你們能先想一想本身是否知道這些問題的答案,而後對比我給出的答案,若是不一致思考下到底誰的答案有問題,動手證實。 這種經過攻克一個個問題從而提高本身的能力,就是我但願經過一篇篇文章給你們帶來的價值。app

每篇文章 我會聚焦於一兩個點,儘可能不擴散太多。固然技術類文章沒法避免一個知識點牽連另外一個知識點。這種碰到遇到不懂知識點的時候我建議你們知道有這麼個東西,把精力集中於當前問題,而不要太擴散。固然後續我也會根據反饋,進行填坑。框架

問題

  1. 什麼是Index Nested-Loop Join?
  2. 什麼是Block Nested-Loop Join?
  3. join buffer 作什麼用的?
  4. 工做當中到底應不該該用join ?


準備工做

CREATE TABLE `school` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `school_id` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_school` (`school_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

delimiter;;
CREATE PROCEDURE idata ( ) BEGIN
    DECLARE
        i INT;
    
    SET i = 1;
    WHILE
            ( i <= 1000 ) DO
            INSERT INTO school
        VALUES
            ( i, 'school' );
        SET i = i + 1;
        
    END WHILE;
    
END;;

delimiter;
CALL idata ( );


drop procedure idata;
delimiter;;
CREATE PROCEDURE idata ( ) BEGIN
    DECLARE
        i INT;
    
    SET i = 1;
    WHILE
            ( i <= 1000 ) DO
            INSERT INTO student
        VALUES
            ( i, i%2+1, 'stu');
        SET i = i + 1;
        
    END WHILE;
    
END;;

delimiter;
CALL idata ( );複製代碼

如今有兩張表,一張 shool表,一張 student表。 各插入1000條數據oop

Index Nexted-Loop Join

EXPLAIN select * from student STRAIGHT_JOIN school on school.id = student.school_id複製代碼

(使用STRAIGHT_JOIN) 是防止Mysql 優化器自動優化,具體優化邏輯此處不展開,後續有機會詳聊。優化

執行結果:

image.png

能夠看到student 爲 驅動表, school 爲 被驅動表.

執行流程:

  1. 首先對student 作了全表掃表,這裏掃描了1000行。
  2. 循環遍歷這1000行, 拿一行的 school_id 去 表 school 中查找對應的數據。找到匹配的數據 ,放入結果集中。 因爲school 的id 爲主鍵,因此使用主鍵索引可以迅速定位到那一行。
  3. 循環遍歷結束,輸出結果集到客戶端。

上面使用的是join 也就是內鏈接,若是換成Left join 或者 Right join 外鏈接有什麼區別呢?區別就在於上面第二個步驟中,內鏈接須要找到匹配的數據放到結果集中, 而外鏈接是無論有沒有找到數據,都須要把驅動表的那行數據加入到結果集中。

那麼無論內鏈接仍是外鏈接, 這種拿到驅動表數據 再去循環遍歷查詢被驅動表,而且被驅動表查詢過程當中使用到了索引的方式稱爲 Index Nested-Loop Join 。(從英文其實也能夠看出意思了, Index 索引,Nested-loop 嵌套循環)



Block Nested-Loop Join

那麼若是 被驅動表上的搜索的字段沒有索引呢?

EXPLAIN select * from student STRAIGHT_JOIN  school on school.name = student.name複製代碼

(雖然現實場景中不會存在 用學校名稱 字段關聯學生名稱這種狀況,這裏只是說明若是 學校的name 這個沒有索引的字段被拿來作關聯條件時的狀況)

image.png

能夠看到 school 由於沒法用到索引,因此必需要進行全表掃表。

那麼咱們猜想的執行流程是:

  1. 掃描student 表,取出1000行。
  2. 對取出的1000行數據進行循環遍歷,每一行都 去school 表中 進行搜索,因爲name 字段沒有索引,因此要找到對應的數據須要掃描school全表。 找到數據,放入結果集中。
  3. 輸出結果集。

這個過程 咱們能夠看到 須要掃描的行數爲 1000 * 1000 。 若是school 或者student 數量再大必定,1萬的學校,幾百萬的學生,那麼這個查詢的效率可想而知會很是低。

因此針對這種被驅動表沒法使用索引的join,Mysql 作出了優化,並非咱們上面所猜想的流程。


join buffer

首先,mysql 引入了join buffer 的概念。這個join buffer的大小是能夠經過啓動參數或者系統變量join_buffer_size進行配置,默認大小是256K,最小能夠設置爲128字節。

因此真實的流程爲:

  1. 掃描驅動表student,取出1000行 放入join buffer。
  2. 掃描被驅動表school, 取出其中一行, 與 join_buffer 中的數據作對比, 若是知足了join 的條件,那麼加入到結果集中。 對比完一行取下一行,直到全表掃描結束。

因此上面的情形是 school 中的數據,一行行的取出來 去與 join_buffer 中的數據作對比,查找到 符合條件的結果。那麼 須要掃描的行數是 school 的1000 行, 每一行又要與join_buffer 中的1000行進行對比。

這種狀況 須要掃描的行數爲 student 的1000行,用來放入join_buffer , 和 school 的1000行。也就是掃描了2000行。 單內存中進行判斷的次數是 1000 * 1000;

能夠看到經過引入join buffer 將 磁盤的掃描 變成了內存的判斷,以此來提高效率


若是驅動表數據很大,致使join buffer 放不下呢? 例如 student 有 10000萬行,join buffer 每次只能放1000行。

那麼流程就會變爲:

1.掃描 student ,直到join buffer 放滿。

2.掃描 school ,取出其中一行, 與 join_buffer 中的數據作對比, 若是知足了join 的條件,那麼加入到結果集中。

  1. school 掃描結束後,清空join buffer。
  2. 繼續掃描student 剩下的, 循環 2,3 步驟。直到student 所有被掃描結束。

這種按 一塊塊 進行讀取放入join buffer 並比較的方式 就是Block Nested-Loop Join 了。

優化方式

能夠看到若是 join 的時候使用了Block Nested-Loop Join,那麼它的執行效率與join buffer 的大小相關,join buffer 越大,分塊的次數越小, 效率越高。

固然充分利用join buffer 空間也是一種方式, 放入join buffer 空間的數據是 驅動表的 查詢字段 和 搜索字段。因此避免使用 * ,列清楚須要查詢的字段也有利於 join buffer 空間的利用。

固然最理想的優化方式是添加索引,把 Block Nested-Loop Join 轉爲 Index Nested-Loop Join

索引也不是隨便添加,若是業務場景中不多拿這個字段來進行搜索,單單爲了某個join 而添加索引也得不償失,還須要根據具體業務來考量。


結語

根據join 過程當中 被驅動表 能不能用到索引 將查詢方式分爲 Index Nested-Loop JoinBlock Nested-Loop Join

  • Index Nested-Loop Join 的效率高,能夠在業務中使用。
  • Block Nested-Loop Join 雖然mysql 引入join buffer 來進行優化,可是效率仍是偏低,應該儘可能避免。

ps: 我的認爲在實際業務場景中即便多個表格進行join 過程當中都使用到了Index Nested-Loop Join 的方式,可是因爲每次join 都是進行笛卡爾積的過程,若是其中一張或多張表隨着業務發展數據量增長,會致使 整個sql 查詢效率也不是很理想。 因此仍是儘可能避免多表join 的場景。最多容許2 到 3個表格進行join 。


喜歡,支持請關注個人我的公衆號

qrcode_for_gh_9e5fcea888f2_430.jpg

相關文章
相關標籤/搜索