8.Mysql-join用法


常見用法

JOIN的含義就如英文單詞“join”一樣,連接兩張表,大致分為內連接,外連接,右連接,左連接,自然連接

 CREATE TABLE t_blog(
        id INT PRIMARY KEY AUTO_INCREMENT,
        title VARCHAR(50),
        typeId INT
    );
    -- 博客的類別
    CREATE TABLE t_type(
        id INT PRIMARY KEY AUTO_INCREMENT,
        name VARCHAR(20)
    );

笛卡爾積:CROSS JOIN

要理解各種JOIN首先要理解笛卡爾積。笛卡爾積就是將A表的每一條記錄與B表的每一條記錄強行拼在一起。所以,如果A表有n條記錄,B表有m條記錄,笛卡爾積產生的結果就會產生n*m條記錄。下面的例子,t_blog有10條記錄,t_type有5條記錄,所有他們倆的笛卡爾積有50條記錄。有五種產生笛卡爾積的方式如下。

SELECT * FROM t_blog CROSS JOIN t_type;
SELECT * FROM t_blog INNER JOIN t_type;
SELECT * FROM t_blog,t_type;
SELECT * FROM t_blog NATURE JOIN t_type;
select * from t_blog NATURA join t_type;

內連接:INNER JOIN

內連接INNER JOIN是最常用的連接操作。從數學的角度講就是求兩個表的交集,從笛卡爾積的角度講就是從笛卡爾積中挑出ON子句條件成立的記錄。有INNER JOIN,WHERE(等值連接),STRAIGHT_JOIN,JOIN(省略INNER)四種寫法。示例如下。

SELECT * FROM t_blog INNER JOIN t_type ON t_blog.typeId=t_type.id;
SELECT * FROM t_blog,t_type WHERE t_blog.typeId=t_type.id;
SELECT * FROM t_blog STRAIGHT_JOIN t_type ON t_blog.typeId=t_type.id; #--注意STRIGHT_JOIN有個下划線
SELECT * FROM t_blog JOIN t_type ON t_blog.typeId=t_type.id;

左連接:LEFT JOIN

左連接LEFT JOIN的含義就是求兩個表的交集外加左表剩下的數據。依舊從笛卡爾積的角度講,就是先從笛卡爾積中挑出ON子句條件成立的記錄,然后加上左表中剩余的記錄(見最后三條)。

SELECT * FROM t_blog LEFT JOIN t_type ON t_blog.typeId=t_type.id;
    +----+-------+--------+------+------+
    | id | title | typeId | id   | name |
    +----+-------+--------+------+------+
    |  1 | aaa   |      1 |    1 | C++  |
    |  2 | bbb   |      2 |    2 | C    |
    |  7 | ggg   |      2 |    2 | C    |
    |  3 | ccc   |      3 |    3 | Java |
    |  6 | fff   |      3 |    3 | Java |
    |  4 | ddd   |      4 |    4 | C#   |
    |  5 | eee   |      4 |    4 | C#   |
    |  8 | hhh   |   NULL | NULL | NULL |
    |  9 | iii   |   NULL | NULL | NULL |
    | 10 | jjj   |   NULL | NULL | NULL |
    +----+-------+--------+------+------+

右連接:RIGHT JOIN

同理右連接RIGHT JOIN就是求兩個表的交集外加右表剩下的數據。再次從笛卡爾積的角度描述,右連接就是從笛卡爾積中挑出ON子句條件成立的記錄,然后加上右表中剩余的記錄(見最后一條)。

SELECT * FROM t_blog RIGHT JOIN t_type ON t_blog.typeId=t_type.id;

    +------+-------+--------+----+------------+
    | id   | title | typeId | id | name       |
    +------+-------+--------+----+------------+
    |    1 | aaa   |      1 |  1 | C++        |
    |    2 | bbb   |      2 |  2 | C          |
    |    3 | ccc   |      3 |  3 | Java       |
    |    4 | ddd   |      4 |  4 | C#         |
    |    5 | eee   |      4 |  4 | C#         |
    |    6 | fff   |      3 |  3 | Java       |
    |    7 | ggg   |      2 |  2 | C          |
    | NULL | NULL  |   NULL |  5 | Javascript |
    +------+-------+--------+----+------------+

外連接:OUTER JOIN

外連接就是求兩個集合的並集。從笛卡爾積的角度講就是從笛卡爾積中挑出ON子句條件成立的記錄,然后加上左表中剩余的記錄,最后加上右表中剩余的記錄。另外MySQL不支持OUTER JOIN,但是我們可以對左連接和右連接的結果做UNION操作來實現。

SELECT * FROM t_blog LEFT JOIN t_type ON t_blog.typeId=t_type.id
    UNION
SELECT * FROM t_blog RIGHT JOIN t_type ON t_blog.typeId=t_type.id;

USING子句

MySQL中連接SQL語句中,ON子句的語法格式為:table1.column_name = table2.column_name。當模式設計對聯接表的列采用了相同的命名樣式時,就可以使用 USING 語法來簡化 ON 語法,格式為:USING(column_name)。 所以,USING的功能相當於ON,區別在於USING指定一個屬性名用於連接兩個表,而ON指定一個條件。另外,SELECT *時,USING會去除USING指定的列,而ON不會。實例如下。

SELECT * FROM t_blog INNER JOIN t_type ON t_blog.typeId =t_type.id;
    +----+-------+--------+----+------+
    | id | title | typeId | id | name |
    +----+-------+--------+----+------+
    |  1 | aaa   |      1 |  1 | C++  |
    |  2 | bbb   |      2 |  2 | C    |
    |  7 | ggg   |      2 |  2 | C    |
    |  3 | ccc   |      3 |  3 | Java |
    |  6 | fff   |      3 |  3 | Java |
    |  4 | ddd   |      4 |  4 | C#   |
    |  5 | eee   |      4 |  4 | C#   |
    +----+-------+--------+----+------+


    SELECT * FROM t_blog INNER JOIN t_type USING(typeId);
    ERROR 1054 (42S22): Unknown column 'typeId' in 'from clause'
    SELECT * FROM t_blog INNER JOIN t_type USING(id); -- 應為t_blog的typeId與t_type的id不同名,無法用Using,這里用id代替下。
    +----+-------+--------+------------+
    | id | title | typeId | name       |
    +----+-------+--------+------------+
    |  1 | aaa   |      1 | C++        |
    |  2 | bbb   |      2 | C          |
    |  3 | ccc   |      3 | Java       |
    |  4 | ddd   |      4 | C#         |
    |  5 | eee   |      4 | Javascript |
    +----+-------+--------+------------+

自然連接:NATURE JOIN

自然連接就是USING子句的簡化版,它找出兩個表中相同的列作為連接條件進行連接。有左自然連接右自然連接普通自然連接之分。在t_blog和t_type示例中,兩個表相同的列是id,所以會拿id作為連接條件。 另外千萬分清下面三條語句的區別 。 自然連接:SELECT * FROM t_blog NATURAL JOIN t_type; 笛卡爾積:SELECT * FROM t_blog NATURA JOIN t_type; 笛卡爾積:SELECT * FROM t_blog NATURE JOIN t_type;

SELECT * FROM t_blog NATURAL JOIN t_type;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog,t_type WHERE t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog INNER JOIN t_type ON t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog INNER JOIN t_type USING(id);

    +----+-------+--------+------------+
    | id | title | typeId | name       |
    +----+-------+--------+------------+
    |  1 | aaa   |      1 | C++        |
    |  2 | bbb   |      2 | C          |
    |  3 | ccc   |      3 | Java       |
    |  4 | ddd   |      4 | C#         |
    |  5 | eee   |      4 | Javascript |
    +----+-------+--------+------------+

SELECT * FROM t_blog NATURAL LEFT JOIN t_type;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog LEFT JOIN t_type ON t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog LEFT JOIN t_type USING(id);

    +----+-------+--------+------------+
    | id | title | typeId | name       |
    +----+-------+--------+------------+
    |  1 | aaa   |      1 | C++        |
    |  2 | bbb   |      2 | C          |
    |  3 | ccc   |      3 | Java       |
    |  4 | ddd   |      4 | C#         |
    |  5 | eee   |      4 | Javascript |
    |  6 | fff   |      3 | NULL       |
    |  7 | ggg   |      2 | NULL       |
    |  8 | hhh   |   NULL | NULL       |
    |  9 | iii   |   NULL | NULL       |
    | 10 | jjj   |   NULL | NULL       |
    +----+-------+--------+------------+

SELECT * FROM t_blog NATURAL RIGHT JOIN t_type;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog RIGHT JOIN t_type ON t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog RIGHT JOIN t_type USING(id);

    +----+------------+-------+--------+
    | id | name       | title | typeId |
    +----+------------+-------+--------+
    |  1 | C++        | aaa   |      1 |
    |  2 | C          | bbb   |      2 |
    |  3 | Java       | ccc   |      3 |
    |  4 | C#         | ddd   |      4 |
    |  5 | Javascript | eee   |      4 |
    +----+------------+-------+--------+

補充

開頭給出的第一張圖除去講了的內連接、左連接、右連接、外連接,還有一些特殊的韋恩圖,這里補充一下。

 SELECT * FROM t_blog LEFT JOIN t_type ON t_blog.typeId=t_type.id
    WHERE t_type.id IS NULL;
    +----+-------+--------+------+------+
    | id | title | typeId | id   | name |
    +----+-------+--------+------+------+
    |  8 | hhh   |   NULL | NULL | NULL |
    |  9 | iii   |   NULL | NULL | NULL |
    | 10 | jjj   |   NULL | NULL | NULL |
    +----+-------+--------+------+------+
    SELECT * FROM t_blog RIGHT JOIN t_type ON t_blog.typeId=t_type.id
    WHERE t_blog.id IS NULL;
    +------+-------+--------+----+------------+
    | id   | title | typeId | id | name       |
    +------+-------+--------+----+------------+
    | NULL | NULL  |   NULL |  5 | Javascript |
    +------+-------+--------+----+------------+
    SELECT * FROM t_blog LEFT JOIN t_type ON t_blog.typeId=t_type.id
    WHERE t_type.id IS NULL
    UNION
    SELECT * FROM t_blog RIGHT JOIN t_type ON t_blog.typeId=t_type.id
    WHERE t_blog.id IS NULL;
    +------+-------+--------+------+------------+
    | id   | title | typeId | id   | name       |
    +------+-------+--------+------+------------+
    |    8 | hhh   |   NULL | NULL | NULL       |
    |    9 | iii   |   NULL | NULL | NULL       |
    |   10 | jjj   |   NULL | NULL | NULL       |
    | NULL | NULL  |   NULL |    5 | Javascript |
    +------+-------+--------+------+------------+

Join 原理

表連接算法

Nested Loop Join(NLJ)算法:

首先介紹一種基礎算法:NLJ,嵌套循環算法。循環外層是驅動表,循壞內層是被驅動表。驅動表會驅動被驅動表進行連接操作。首先驅動表找到第一條記錄,然后從頭掃描被驅動表,逐一查找與驅動表第一條記錄匹配的記錄然后連接起來形成結果表中的一條記錄。被驅動表查找完后,再從驅動表中取出第二個記錄,然后從頭掃描被驅動表,逐一查找與驅動表第二條記錄匹配的記錄,連接起來形成結果表中的一條記錄。重復上述操作,直到驅動表的全部記錄都處理完畢為止。這就是嵌套循環連接算法的基本思想,偽代碼如下。

  foreach row1 from t1
        foreach row2 from t2
            if row2 match row1 //row2與row1匹配,滿足連接條件
                join row1 and row2 into result //連接row1和row2加入結果集

首先加載t1,然后從t1中取出第一條記錄,之后加載t2表,與t2表中的記錄逐個匹配,連接匹配的記錄。

Block Nested Loop Join(BNLJ)算法:

再介紹一種高級算法:BNLJ,塊嵌套循環算法,可以看作對NLJ的優化。大致思想就是建立一個緩存區,一次從驅動表中取多條記錄,然后掃描被驅動表,被驅動表的每一條記錄都嘗試與緩沖區中的多條記錄匹配,如果匹配則連接並加入結果集。緩沖區越大,驅動表一次取出的記錄就越多。這個算法的優化思路就是減少內循環的次數從而提高表連接效率。

影響性能的因素

1.內循環的次數:現在考慮這么一個場景,當t1有100條記錄,t2有10000條記錄。那么,t1驅動t2與t2驅動t1,他們之間在效率上孰優孰劣?如果是單純的分析指令執行次數,他們都是100*10000,但是考慮到加載表的次數呢。首先分析t1驅動t2,t1表加載1次,t2表需要加載100次。然后分析t2驅動t1,t2表首先加載1次,但是t1表要加載10000次。所以,t1驅動t2的效率要優於t2驅動t1的效率。由此得出,小表驅動大表能夠減少內循環的次數從而提高連接效率。 另外,如果使用Block Nested Loop Join算法的話,通過擴大一次緩存區的大小也能減小內循環的次數。由此又可得,設置合理的緩沖區大小能夠提高連接效率

2.快速匹配:掃描被驅動表尋找合適的記錄可以看做一個查詢操作,如何提高查詢的效率呢?建索引啊!由此還可得出,在被驅動表建立索引能夠提高連接效率

3.排序:假設t1表驅動t2表進行連接操作,連接條件是t1.id=t2.id,而且要求查詢結果對id排序。現在有兩種選擇,方式一[...ORDER BY t1.id],方式二[...ORDER BY t2.id]。如果我們使用方式一的話,可以先對t1進行排序然后執行表連接算法,如果我們使用方式二的話,只能在執行表連接算法后,對結果集進行排序(Using temporary),效率自然低下。由此最后可得出,優先選擇驅動表的屬性進行排序能夠提高連接效率。

JOIN優化實踐之內循環的次數

如何優化內循環的次數。內循環的次數受驅動表的記錄數所影響,驅動表記錄數越多,內循環就越多,連接效率就越低下,所以盡量用小表驅動大表。先插入測試數據。

 CREATE TABLE t1 (
        id INT PRIMARY KEY AUTO_INCREMENT,
        type INT
    );#10000
    CREATE TABLE t2 (
        id INT PRIMARY KEY AUTO_INCREMENT,
        type INT
    );#100

內連接誰當驅動表

實際業務場景中,左連接、右連接可以根據業務需求認定誰是驅動表,誰是被驅動表。但是內連接不同,根據嵌套循環算法的思想,t1內連接t2和t2內連接t1所得結果集是相同的。那么到底是誰連接誰呢?謹記一句話即可,小表驅動大表可以減小內循環的次數。下面用 STRAIGHT_JOIN強制左表連接右表。By the way,STRIGHT_JOIN比較冷門,在這里解釋下,其作用相當於內連接,不過強制規定了左表驅動右邊

 EXPLAIN SELECT * FROM t1 STRAIGHT_JOIN t2 ON t1.type=t2.type;
    +----+-------+------+------+-------+----------------------------------------------------+
    | id | table | type | key  | rows  | Extra                                              |
    +----+-------+------+------+-------+----------------------------------------------------+
    |  1 | t1    | ALL  | NULL | 10000 | NULL                                               |
    |  1 | t2    | ALL  | NULL |   100 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------+------+------+-------+----------------------------------------------------+
    EXPLAIN SELECT * FROM t2 STRAIGHT_JOIN t1 ON t2.type=t1.type;
    +----+-------+------+------+-------+----------------------------------------------------+
    | id | table | type | key  | rows  | Extra                                              |
    +----+-------+------+------+-------+----------------------------------------------------+
    |  1 | t2    | ALL  | NULL |   100 | NULL                                               |
    |  1 | t1    | ALL  | NULL | 10000 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------+------+------+-------+----------------------------------------------------+

對於第一條查詢語句,t1是驅動表,其有10000條記錄,內循環也就有10000次,這還得了? 對於第二條查詢語句,t2是驅動表,其有100條記錄,內循環100次,感覺不錯,我喜歡! 這些SQL語句的執行時間也說明了,當內連接時,務必用小表驅動大表

最佳實踐:直接讓MySQL去判斷

但是,表的記錄數是會變化的,有沒有一勞永逸的寫法?當然有啦,MySQL自帶的Optimizer會優化內連接,優化策略就是上面講的小表驅動大表。所以,以后寫內連接不要糾結誰內連接誰了,直接讓MySQL去判斷吧。

##下面6條內連接SQL,MySQL的Optimizer都會進行優化。
 EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.type=t2.type;
 EXPLAIN SELECT * FROM t2 INNER JOIN t1 ON t1.type=t2.type;
 EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.type=t2.type;
 EXPLAIN SELECT * FROM t2 JOIN t1 ON t1.type=t2.type;
 EXPLAIN SELECT * FROM t1,t2 WHERE t1.type=t2.type;
 EXPLAIN SELECT * FROM t2,t1 WHERE t1.type=t2.type;
    +----+-------+------+------+--------+----------------------------------------------------+
    | id | table | type | key  | rows   | Extra                                              |
    +----+-------+------+------+--------+----------------------------------------------------+
    |  1 | t2    | ALL  |  NULL|    100 | NULL                                               |
    |  1 | t1    | ALL  | NULL | 110428 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------+------+------+--------+----------------------------------------------------+

JOIN優化實踐之快速匹配

如何優化掃描速度。我們通過JOIN原理得知了兩張表的JOIN操作就是不斷從驅動表中取出記錄,然后查找出被驅動表中與之匹配的記錄並連接。這個過程的實質就是查詢操作,想要優化查詢操作,建索引是最常用的方式。那索引怎么建呢?我們來討論下,首先插入測試數據。

   CREATE TABLE t1 (
        id INT PRIMARY KEY AUTO_INCREMENT,
        type INT
    );#110000
    CREATE TABLE t2 (
        id INT PRIMARY KEY AUTO_INCREMENT,
        type INT
    );#100

左連接

左連接中,左表是驅動表,右表是被驅動表。想要快速查找被驅動表中匹配的記錄,所以我們可以在右表建索引,從而提高連接性能

  -- 首先兩個表都沒建索引
    EXPLAIN SELECT * FROM t1 LEFT JOIN t2 ON t1.type=t2.type;
    +----+-------+------+------+--------+----------------------------------------------------+
    | id | table | type | key  | rows   | Extra                                              |
    +----+-------+------+------+--------+----------------------------------------------------+
    |  1 | t1    | ALL  | NULL | 110428 | NULL                                               |
    |  1 | t2    | ALL  | NULL |    100 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------+------+------+--------+----------------------------------------------------+

    -- 嘗試在左表建立索引,改進不大
    CREATE INDEX idx_type ON t1(type);
    EXPLAIN SELECT * FROM t1 LEFT JOIN t2 ON t1.type=t2.type;
    +----+-------+-------+----------+--------+----------------------------------------------------+
    | id | table | type  | key      | rows   | Extra                                              |
    +----+-------+-------+----------+--------+----------------------------------------------------+
    |  1 | t1    | index | idx_type | 110428 | Using index                                        |
    |  1 | t2    | ALL   | NULL     |    100 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------+-------+----------+--------+----------------------------------------------------+


    -- 嘗試在右表建立索引,效果拔群,Using index!!!
    DROP INDEX idx_type ON t1;
    CREATE INDEX idx_type ON t2(type);
    EXPLAIN SELECT * FROM t1 LEFT JOIN t2 ON t1.type=t2.type;
    +----+-------+------+---------------+----------+--------+-------------+
    | id | table | type | possible_keys | key      | rows   | Extra       |
    +----+-------+------+---------------+----------+--------+-------------+
    |  1 | t1    | ALL  | NULL          | NULL     | 110428 | NULL        |
    |  1 | t2    | ref  | idx_type      | idx_type |      1 | Using index |
    +----+-------+------+---------------+----------+--------+-------------+

右連接

右連接中,右表是驅動表,左表是被驅動表,想要快速查找被驅動表中匹配的記錄,所以我們可以在左表建索引,從而提高連接性能。

DROP INDEX idx_type ON t2;
    -- 兩個表都沒有索引
    EXPLAIN SELECT * FROM t1 RIGHT JOIN t2 ON t1.type=t2.type;
    +----+-------+------+------+--------+----------------------------------------------------+
    | id | table | type | key  | rows   | Extra                                              |
    +----+-------+------+------+--------+----------------------------------------------------+
    |  1 | t2    | ALL  | NULL |    100 | NULL                                               |
    |  1 | t1    | ALL  | NULL | 110428 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------+------+------+--------+----------------------------------------------------+


    -- 在右邊建立索引,改進不大
    CREATE INDEX idx_type ON t2(type);
    EXPLAIN SELECT * FROM t1 RIGHT JOIN t2 ON t1.type=t2.type;
    +----+-------+-------+---------------+----------+--------+----------------------------------------------------+
    | id | table | type  | possible_keys | key      | rows   | Extra                                              |
    +----+-------+-------+---------------+----------+--------+----------------------------------------------------+
    |  1 | t2    | index | NULL          | idx_type |    100 | Using index                                        |
    |  1 | t1    | ALL   | NULL          | NULL     | 110428 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------+-------+---------------+----------+--------+----------------------------------------------------+


    -- 嘗試在左邊建立索引,效果拔群!
    DROP INDEX idx_type ON t2;
    CREATE INDEX idx_type ON t1(type);
    EXPLAIN SELECT * FROM t1 RIGHT JOIN t2 ON t1.type=t2.type;
    +----+-------+------+---------------+--------------+------+-------------+
    | id | table | type | possible_keys | ref          | rows | Extra       |
    +----+-------+------+---------------+--------------+------+-------------+
    |  1 | t2    | ALL  | NULL          | NULL         |  100 | NULL        |
    |  1 | t1    | ref  | idx_type      | test.t2.type |    5 | Using index |
    +----+-------+------+---------------+--------------+------+-------------+

內連接

我們知道,MySQL Optimizer會對內連接做優化,不管誰內連接誰,都是用小表驅動大表,所以如果要優化內連接,可以在大表上建立索引,以提高連接性能。另外注意一點,在小表上建立索引時,MySQL Optimizer會認為用大表驅動小表效率更快,轉而用大表驅動小表。對內連接小表驅動大表的優化策略不清楚的話,可以看JOIN優化實踐之內循環的次數

 DROP INDEX idx_type ON t1;
    -- 兩個表都沒有索引,t2驅動t1
    EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.type=t2.type;
    +----+-------+------+------+--------+----------------------------------------------------+
    | id | table | type | key  | rows   | Extra                                              |
    +----+-------+------+------+--------+----------------------------------------------------+
    |  1 | t2    | ALL  | NULL |    100 | NULL                                               |
    |  1 | t1    | ALL  | NULL | 110428 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------+------+------+--------+----------------------------------------------------+

    -- 在t2表上建立索引,MySQL的Optimizer發現后,用大表驅動了小表
    CREATE INDEX idx_type ON t2(type);
    EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.type=t2.type;
    +----+-------+------+----------+--------+-------------+
    | id | table | type | key      | rows   | Extra       |
    +----+-------+------+----------+--------+-------------+
    |  1 | t1    | ALL  | NULL     | 110428 | Using where |
    |  1 | t2    | ref  | idx_type |      1 | Using index |
    +----+-------+------+----------+--------+-------------+


    -- 在t1表上建立索引,再加上t1是大表,符合“小表驅動大表”的原則,性能比上面的語句要好
    DROP INDEX idx_type ON t2;
    CREATE INDEX idx_type ON t1(type);
    EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.type=t2.type;
    +----+-------+------+---------------+----------+------+-------------+
    | id | table | type | possible_keys | key      | rows | Extra       |
    +----+-------+------+---------------+----------+------+-------------+
    |  1 | t2    | ALL  | NULL          | NULL     |  100 | Using where |
    |  1 | t1    | ref  | idx_type      | idx_type |    5 | Using index |
    +----+-------+------+---------------+----------+------+-------------+

三表連接

上面都是兩表連接,三表連接也是一樣的,找出驅動表和被驅動表,在被驅動表上建立索引,即可提高連接性能。

總結

想要從快速匹配的角度優化JOIN,首先就是找出誰是驅動表,誰是被驅動表,然后在被驅動表上建立索引即可。

JOIN優化實踐之排序

如何優化JOIN查詢帶有排序的情況。大致分為對連接屬性排序對非連接屬性排序兩種情況。插入測試數據。

  CREATE TABLE t1 (
        id INT PRIMARY KEY AUTO_INCREMENT,
        type INT
    );# 10000 
    CREATE TABLE t2 (
        id INT PRIMARY KEY AUTO_INCREMENT,
        type INT
    );# 100

對連接屬性進行排序

現要求對t1和t2做內連接,連接條件是t1.id=t2.id,並對連接屬性id屬性進行排序(MySQL為主鍵id建立了索引)。

有兩種選擇,方式一[...ORDER BY t1.id],方式二[...ORDER BY t2.id],選哪種呢?

首先我們找出驅動表和被驅動表,按照小表驅動大表的原則,大表是t1,小表是t2,所以t2是驅動表,t1是非驅動表,t2驅動t1。然后進行分析,如果我們使用方式一的話,MySQL會先對t1進行排序然后執行表連接算法,如果我們使用方式二的話,只能執行表連接算法后對結果集進行排序(extra:using temporary),效率必然低下。

所以,當對連接屬性進行排序時,應當選擇驅動表的屬性作為排序表中的條件

 -- 對被驅動表字段進行排序
    EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.id =t2.id ORDER BY t1.id;
    +----+-------+--------+---------+------+---------------------------------+
    | id | table | type   | key     | rows | Extra                           |
    +----+-------+--------+---------+------+---------------------------------+
    |  1 | t2    | ALL    | NULL    |  100 | Using temporary; Using filesort |
    |  1 | t1    | eq_ref | PRIMARY |    1 | NULL                            |
    +----+-------+--------+---------+------+---------------------------------+

    -- 對驅動表字段進行排序,沒有Using temporary,也沒有Using filesort 
    EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.id =t2.id ORDER BY t2.id;
    +----+-------+--------+---------+------+-------+
    | id | table | type   | key     | rows | Extra |
    +----+-------+--------+---------+------+-------+
    |  1 | t2    | index  | PRIMARY |  100 | NULL  |
    |  1 | t1    | eq_ref | PRIMARY |    1 | NULL  |
    +----+-------+--------+---------+------+-------+

對非連接屬性進行排序

現要求對t1和t2做內連接,連接條件是t1.id=t2.id,並對非連接屬性t1的type屬性進行排序,[...ORDER BY t1.type]。

首先我們找出驅動表和被驅動表,按照小表驅動大表的原則,大表是t1,小表是t2,所以MySQL Optimizer會用t2驅動t1。現在我們要對t1的type屬性進行排序,t1是被驅動表,必然導致對連接后結果集進行排序Using temporary(比Using filesort更嚴重)。所以,能不能不用MySQL Optimizer,用大表驅動小表呢? 有請STRAIGHT_JOIN!

 EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.id =t2.id ORDER BY t1.type;
    +----+-------+--------+---------+------+---------------------------------+
    | id | table | type   | key     | rows | Extra                           |
    +----+-------+--------+---------+------+---------------------------------+
    |  1 | t2    | ALL    | NULL    |  100 | Using temporary; Using filesort |
    |  1 | t1    | eq_ref | PRIMARY |    1 | NULL                            |
    +----+-------+--------+---------+------+---------------------------------+
    -- Using temporary沒有了,但是大表驅動小表,導致內循環次數增加,實際開發中要從實際出發,
    -- 對此作出權衡。
    EXPLAIN SELECT * FROM t1 STRAIGHT_JOIN t2 ON t1.id =t2.id ORDER BY t1.type;
    +----+-------+--------+---------+-------+----------------+
    | id | table | type   | key     | rows  | Extra          |
    +----+-------+--------+---------+-------+----------------+
    |  1 | t1    | ALL    | NULL    | 10000 | Using filesort |
    |  1 | t2    | eq_ref | PRIMARY |     1 | NULL           |
    +----+-------+--------+---------+-------+----------------+

最后在常見用法那里挖了個坑,現在填上:INNER JOIN、JOIN、WHERE等值連接和STRAIGHT_JOIN都能表示內連接,那平時如何選擇呢?一般情況下用INNER JOIN、JOIN或者WHERE等值連接,因為MySQL Optimizer會按照“小表驅動大表的策略”進行優化。當出現上述問題時,才考慮用STRAIGHT_JOIN

 

參考總結於:https://www.cnblogs.com/fudashi/p/7491039.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM