【MySql專欄】—— 關聯查詢join的流程以及優化

一、應不應該使用關聯查詢?

對於關聯查詢來說,並不是所有情況下都能使用的,有的公司會直接禁用關聯查詢,因爲使用關聯查詢後,那麼後序在項目升級時,對數據庫進行分庫分表後,關聯查詢就沒辦法在使用,所有代碼都需要重構,不利於後期的維護和重構。本篇文章的前提條件是可以使用關聯查詢,那麼在我們使用關聯查詢的時候需要注意什麼?

爲了後序的驗證,這裏創建兩個表

CREATE TABLE `t1` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
) ENGINE=InnoDB;

create table t2 like t1;

其中:表t1中有100條數據,表t2中有1000條數據

二、關聯查詢使用了索引(Index Nested-Loop Join)

首先我們來看一下,下面這條語句:

select * from t1 left join t2 on t1.a = t2.a;

這條語句的執行流程如下圖:

從中可以分析出來表t1作爲驅動表,沒有走索引,進行了全表掃描,表t2的字段a有索引,走了這個索引。語句執行流程如下:

1.從表t1中讀入一行數據T;

2.從數據行T中找出字段a的值,去表t2中進行查詢;

3.取出表t2滿足的數據行和數據行T組成結果集,返回給客戶端;

4.重複1~3步,直到表t1的數據循環結束。

這個語句掃描的行數流程爲:

1.驅動表t1做全表掃描,掃描100行;

2.根據拿到數據行T的字段a,去表t2中匹配,因爲表t2中字段a創建了索引,這個查詢每次是走樹搜索,基本掃描一行數據搞定,所以也是一共掃描就是100行;

3.因此總共掃描200行數據。

這個時候我們來對比一下不使用join,分開實現這個語句是什麼流程呢:

1.首先使用語句select * from t1 全表掃描100行;

2.根據查詢查詢出來的結果集,分別執行select * from t2 where a = T.a,掃描100行;

3.總計也是掃描200行,但是與數據庫交互了101次比使用join多交互了100次,而且客戶端還有對返回的結果集進行封裝,肯定是要比使用join的效果差。

如果我們使用的是表t2做驅動表,t1做被驅動表呢?

select * from t2 left join t1 on t1.a = t2.a

這時這個實行流程就是:

1.在表t2上走全表掃描,掃描1000行

2.把查詢到的數據行,去表t1做匹配,走樹索引,掃描也要1000行

3.這個使用總計就是2000行。很明顯掃描的行數增加了。

這個使用可以總計一個計算公式:如果驅動表的行數爲N,被驅動表的行數爲M,驅動表走全表掃描,被驅動表走的是索引數的查找,那麼驅動表在查找一條數據後,在被驅動表上走普通索引a,在根據普通索引上的主鍵回表查詢數據,走一次樹搜索的時間複雜度爲log2M,回表一次就是2*log2M,驅動表全表掃描,掃描行數爲N,那麼總掃描行數爲N+N*2*log2M,那麼可以總結出,驅動表的數據越小,整個過程掃描的行數就越小,因此我們應該使用小表作爲驅動表,大表作爲被驅動表。

三、關聯查詢未使用了索引(Simple Nested-Loop Join)

如果我們使用如下語句進行關聯查詢:

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

因爲t2表的字段b沒有索引,那麼簡單語句分析語句執行流程:

1.表t1走全表掃描,掃描100行

2.從t1表中取出字段,到t2去匹配,因爲表t2的字段b上沒有索引,那麼會走全表掃描,這個過程掃描的行數爲100*1000,也就是10萬行數據;

顯然這個結果,對於我們來說是不能接受的,因此mysql針對這個做了優化,我們可以使用explain查看一下語句執行情況:

其中Extra中,使用了Block Nested Loop,那麼使用了Block Nested Loop算法後執行流程如下:

1.從表t1中,全表掃描,取出所有數據100條放入join_buffer中,因爲是select * 所以是把全部數據放入緩存;

2.因爲表t2上字段b沒有索引,把表t2拿出來的數據與join_buffer中的數據進行對比,滿足join條件的放入結果集中;

因此整個過程,表t1和表t2都是全表所描總計1100行,因爲join_buffer中的數據是無須的,因此每次從表t2中拿出一條數據進行比較需要比較100次,因此總比較次數也是100*1000是10次,但是這個比較是在內存中進行的,速度上會快很多,性能上也會更好。

這個使用我們來總結一下,使用什麼表做驅動表會更好,如果小錶行數爲N,大表行數爲M,因爲沒有索引總掃描行數爲N+M,在內存中比較的次數爲N*M,從中可以看出來,無論誰是驅動表,並不影響其性能。

如果表的數據量很大,join_buffer放不下這麼辦呢?

首先我們要直到join_buffer的大小是由join_buffer_size設定的,默認大小爲256k,如果我們的數據量非常大,那麼我們就會進行分段放入join_buffer中,那麼上訴語句的執行流程就會是如下流程:

1.掃描表t1,按順序將數據放入join_buffer中,假設這是隻夠放入80條數據,join_buffer就滿了,繼續步驟2;

2.掃描表t2,將t2中的數據取出,與join_buffer中的數據進行比較,滿足join條件,作爲結果集放回;

3.清空join_buffer的數據;

4.繼續掃描表t1,將剩餘的20行數據放入join_buffer中,繼續執行步驟2;

針對上訴步驟我們再來分析這個執行過程的流程:放入join_buffer的數據爲N行,領一個表爲M行,放入join_buffer的次數爲K,這個K是和N有關係的,N越大,那麼K越大,那麼K 約定與x乘以N,x的取之爲0~1;

1.那麼掃描的總行數爲N + xN*M;

2.比較的總次數爲N*M

所以這個時候x的值越小,掃描的行數也就越小,而x的大小又是和N相關,所以將小表作爲驅動表更合適。

四、是使用left join 還是 right join 還是 inner join呢?

a left join b:表示a是驅動表,b是被驅動表;

a right join b :表示b是驅動表,a是被驅動表;

a inner join b:mysql會自動優化將a b兩個表,小表作爲驅動表,大表作爲被驅動表,因此在開發中,當不確定那個表是小表時,儘量使用inner join來關聯兩張表吧。

 

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