MySQL連線查詢驅動表被驅動表以及效能優化

阿偉~發表於2020-06-14

準備我們需要的表結構和資料

兩張表 studnet(學生)表和score(成績)表, 建立表的SQL語句如下

CREATE TABLE `student` (
  `id` int(11) NOT NULL,
  `no` varchar(20) DEFAULT NULL,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `score` (
  `id` int(11) NOT NULL,
  `no` varchar(20) DEFAULT NULL,
  `chinese` double(4,0) DEFAULT NULL,
  `math` double(4,0) DEFAULT NULL,
  `engilsh` double(4,0) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

MySQL連線查詢分為以下三種

left join 左連線,用法如下,這種查詢會把左表(student)所有資料查詢出來,右表不存在的用空表示,結果圖如下

select * from student s1 left join score  s2 on  s1.on = s2. on

right join 右連線, 用法如下,這種查詢會把右表(score)所有資料查詢出來,左表不存在的用空表示,結果圖如下

select * from student s1 right join score s2 on s1.no = s2.no

inner join 內連線,用法如下,這種查詢會把左右表都存在的資料查詢出來,不存在資料忽略,結果圖如下

select * from student s1 inner join score s2 on s1.no = s2.no

連線查詢中需要注意的點

什麼是驅動表,什麼是被驅動表,這兩個概念在查詢中有時容易讓人搞混,有下面幾種情況,大家需要了解。

  1. 當連線查詢沒有where條件時,左連線查詢時,前面的表是驅動表,後面的表是被驅動表,右連線查詢時相反,內連線查詢時,哪張表的資料較少,哪張表就是驅動表
  2. 當連線查詢有where條件時,帶where條件的表是驅動表,否則是被驅動表

怎麼確定我們上面的兩種情況呢,執行計劃是不會騙人的,我們針對上面情況分別看看執行計劃給出的答案

首先第一種情況,student表中3條資料,score表中2條資料,但兩張表中只有一條資料是關聯的(編號是1),看如下SQL查詢

//左連線查詢
explain select * from student s1 left join score s2 on s1.no = s2.no
//右連線查詢
explain select * from student s1 right join score s2 on s1.no = s2.no
//內連線查詢
explain select * from student s1 inner join score s2 on s1.no = s2.no

執行計劃中靠前的表是驅動表,我們看下面三種圖中,是不是全部符合情況一,第一張圖中s1是驅動表,第二張圖中s2是驅動表,第三種圖中s2是驅動表

其次第二種情況,還是上面三種SQL語句,我們分別加上where條件,再來看看執行計劃的結果是什麼樣呢?

//左連線查詢
explain select * from student s1 left join score s2 on s1.no = s2.no 
where s2. no = 1
//右連線查詢
explain select * from student s1 right join score s2 on s1.no = s2.no 
where s1.no = 1
//內連線查詢
explain select * from student s1 inner join score s2 on s1.no = s2.no 
where s1.no = 1

我們看下面三種執行計劃結果,全都以where條件為準了,而且跟上面情況一的都相反了,因此情況二也是得到了驗證.



連線查詢優化

要理解連線查詢優化,得先理解連線查詢的演算法,連線查詢常用的一共有兩種演算法,我們簡要說明一下

Simple Nested-Loop Join Algorithms (簡單巢狀迴圈連線演算法)

比如上面的查詢中,我們確定了驅動表和被驅動表,那麼查詢過程如下,很簡單,就是雙重迴圈,從驅動表中迴圈獲取每一行資料,再在被驅動表匹配滿足條件的行。

for (row1 : 驅動表) {
    for (row2 : 被驅動表){
        if (conidtion == true){
            send client
        }
    }
}
Index Nested-Loop Join Algorithms (索引巢狀迴圈連線演算法)

上面雙重for迴圈的查詢中,相信很多研發人員看到這種情況第一個想法就是效能問題,是的,join查詢的優化思路就是小表驅動大表,而且在大表上建立索引(也就是被動表建立索引),如果驅動表建立了索引,MySQL是不會使用的

for (row1 : 驅動表) {
    索引在被驅動表中命中,不用再遍歷被驅動表了
}
Block Nested-Loop Join Algorithm(基於塊的連線巢狀迴圈演算法)

其實很簡單就是把一行變成了一批,塊巢狀迴圈(BNL)巢狀演算法使用對在外部迴圈中讀取的行進行緩衝,以減少必須讀取內部迴圈中的表的次數。例如,如果將10行讀入緩衝區並將緩衝區傳遞到下一個內部迴圈,則可以將內部迴圈中讀取的每一行與緩衝區中的所有10行進行比較。這將內部表必須讀取的次數減少了一個數量級。

MySQL連線緩衝區大小通過這個引數控制 : join_buffer_size

MySQL連線緩衝區有一些特徵,只有無法使用索引時才會使用連線緩衝區;聯接中只有感興趣的列儲存在其聯接緩衝區中,而不是整個行;為每個可以緩衝的連線分配一個緩衝區,因此可以使用多個連線緩衝區來處理給定查詢;在執行連線之前分配連線緩衝區,並在查詢完成後釋放連線緩衝區

所以查詢時最好不要把 * 作為查詢的欄位,而是需要什麼欄位查詢什麼欄位,這樣緩衝區能夠緩衝足夠多的行。

從上面的執行計劃中其實我們已經看到了 useing join buffer了,是的,那是因為我們對兩張表都有建立索引

三種演算法優先順序

第一種演算法忽略,MySQL不會採用這種的,當我們對被驅動表建立了索引,那麼MySQL一定使用的第二種演算法,當我們沒有建立索引或者對驅動表建立了索引,那麼MySQL一定使用第三種演算法

MySQL連線演算法官方文件

https://dev.mysql.com/doc/refman/8.0/en/nested-loop-joins.html

相關文章