1. 程式人生 > >MySQL優化系列(二)--查詢優化(2)(外連線、多表聯合查詢以及查詢注意點)

MySQL優化系列(二)--查詢優化(2)(外連線、多表聯合查詢以及查詢注意點)

繼續這一系列,上篇的簡單查詢優化並沒講完,第二點還有查詢注意點以及多表查詢優化呢!!

文章結構:(1)多表查詢步步優化;(2)查詢編寫的注意點。

/*
    補充說明(往後有博友反饋的話,會繼續補充。)
*/

一、2017-7-19:
關於“超大型資料儘可能盡力不要寫子查詢,使用連線(JOIN)去替換它”。
關於這一類的總結,我們要認真去考量,這裡並不是說一定。
1)因為在大型的資料處理中,子查詢是非常常見的,特別是在查詢出來的資料需要進一步處理的情況,無論是可讀性還是效率上,這時候的子查都是更優。
2)然而在一些特定的場景,可以直接從資料庫讀取就可以的,比如一個表(A表 a,b,c欄位,需要內部資料交集)join
自己的效率必然比放一個子查在where中快得多。可參見我給的例子中的拉黑表,好友表(雙方互相喜歡才是好友的表),在查自己的好友列表的時候,或者拉黑列表中。

文章目錄:

(1)多表查詢步步優化

  • 基本連線方法(內連線、外連線以及交叉連線)

    • 內連線:用比較運算子根據每個表共有的列的值匹配兩個表中的行(=或>、<)
    • 外連線之左連線
    • 外連線之右連線
    • 外連線之全外連線
    • 交叉連線
  • 超大型資料儘可能盡力不要寫子查詢,使用連線(JOIN)去替換它(基礎講完,講優化)

  • (3)使用聯合(UNION)來代替手動建立的臨時表
  • 建立索引(下一篇將詳講)

(2)查詢編寫的注意點

大概有9點,詳情見下文。

一、多表查詢步步優化:(使用還是之前的資料庫的表,商品分類表以及商品詳情表)

這裡寫圖片描述

給出的資料庫有基本的資料框架,剩下的幾個假資料我們就自己建立吧。注意此兩表是有外來鍵約束的。

這裡寫圖片描述

(1)基本連線方法(內連線、外連線以及交叉連線):

一)內連線:用比較運算子根據每個表共有的列的值匹配兩個表中的行(=或>、<)

//意思是:檢索商品分類表和商品表“分類描述”相同的行
select
        d.Good_ID ,
        d.Classify_ID,
        d.Good_Name
        from
Commodity_list d inner join commodity_classification c on d.Classify_Description=c.Good_kinds_Name
得到的滿足某一條件的是A,B內部的資料;正因為得到的是內部共有資料,所以連線方式稱為內連線。

這裡寫圖片描述

很容器看出是兩者都滿足才查出

這裡寫圖片描述

二)外連線之左連線

//意思:查得商品分類表的所有資料,以及滿足條件的商品詳情表的資料
select
        *
        from
        commodity_classification c
        left join commodity_list d
        on d.Classify_Description=c.Good_kinds_Name

這裡寫圖片描述

可以看到,首先是左表資料全部羅列,然後有滿足條件的右表資料都會全部羅列出。若兩條右表資料對左表一條資料,則會用對應好的左表資料補足作為一條記錄。

這裡寫圖片描述

左連線升級:

[left join 或者left outer join(等同於left join)] + [where B.column is null]

//就是隻查分類表資料,但是減去跟商品詳情表有聯絡的資料。
select
        *
        from
        commodity_classification c
        left join commodity_list d
        on d.Classify_Description=c.Good_kinds_Name
        where d.Classify_Description is null

這裡寫圖片描述
這裡寫圖片描述

三)外連線之右連線

//意思是查得商品詳情表的所有資料以及在分類描述相同條件下的商品分類表資料
select
        *
        from
        commodity_classification c
        right join commodity_list d
        on d.Classify_Description=c.Good_kinds_Name

這裡寫圖片描述

與左連恰恰相反,首先是右表資料全部羅列,然後有滿足條件的左表資料都會全部羅列出。若兩條左表資料對右表一條資料,則會用對應好的右表資料補足作為一條記錄。

這裡寫圖片描述

右連線升級:

//意思:查詢商品詳情表的所有資料,但是要減去和商品分類表有聯絡的資料
select
        *
        from
        commodity_classification c
        right join commodity_list d
        on d.Classify_Description=c.Good_kinds_Name
        where c.Good_kinds_Name is null  

這裡寫圖片描述

四)外連線之全外連線:

full join (mysql不支援,但是可以用 left join union right join代替)

select
        *
        from
        commodity_classification c
        left join commodity_list d
        on d.Classify_Description=c.Good_kinds_Name
        union
select
        *
        from
        commodity_classification c
        right join commodity_list d
        on d.Classify_Description=c.Good_kinds_Name

這種場景下得到的是滿足某一條件的公共記錄,和獨有的記錄

這裡寫圖片描述

全外連線升級:

select
        *
        from
        commodity_classification c
        left join commodity_list d
        on d.Classify_Description=c.Good_kinds_Name
        where d.Classify_Description is null
        union
select
        *
        from
        commodity_classification c
        right join commodity_list d
        on d.Classify_Description=c.Good_kinds_Name
         where c.Good_kinds_Name is null

這種場景下得到的是A,B中不滿足某一條件的記錄之和.

這裡寫圖片描述

全部一起列出:消除重複項

這裡寫圖片描述

五)交叉連線:

交叉聯接返回左表中的所有行,左表中的每一行與右表中的所有行組合。交叉聯接也稱作笛卡爾積。 FROM 子句中的表或檢視可通過內聯接或完整外部聯接按任意順序指定;但是,用左或右向外聯接指定表或檢視時,表或檢視的順序很重要。有關使用左或右向外聯接排列表的更多資訊,請參見使用外聯接。

有兩種情況,顯式的和隱式的,不帶ON子句,返回的是兩表的乘積,也叫笛卡爾積。

1. 第一種方式(顯式的交叉連線):A,B表記錄的排列組合,即笛卡兒積。

//可以看到
select
        *
        from
        commodity_classification c
       cross join commodity_list d 

這裡寫圖片描述

補充:cross join可指定條件 (where)

select
        *
        from
        commodity_classification c
       cross join commodity_list d 
       where c.Good_kinds_Name=d.Classify_Description

相當於實現內連線功能了。

這裡寫圖片描述

2. 第二種方式:(隱式的交叉連線,沒有CROSS JOIN)

就跟正上面的效果一樣的語句啦!

select
        *
        from
        commodity_classification c,
        commodity_list d 
       where c.Good_kinds_Name=d.Classify_Description

交叉連線存在的問題遺漏: 參考此博主此博文

mysql對sql語句的容錯問題,即在sql語句不完全符合書寫建議的情況,mysql會允許這種情況,儘可能解釋它:

1)一般cross join後面加上where條件,但是用cross join+on也是被解釋為cross join+where;

2)一般內連線都需要加上on限定條件,如上面場景一;如果不加會被解釋為交叉連線;

3)如果連線表格使用的是逗號,會被解釋為交叉連線;

注:sql標準中還有union join和natural inner join,mysql不支援,而且本身也沒有多大意義,其結果可以用上面的幾種連線方式得到。

(2)超大型資料儘可能盡力不要寫子查詢,使用連線(JOIN)去替換它(基礎講完,講優化):

子查詢:

定義:在一個表表達中可以呼叫另一個表表達式,這個被呼叫的表表達式叫做子查詢(subquery),也稱作子選擇(subselect)或內嵌選擇(inner select)。子查詢的結果傳遞給呼叫它的表表達式繼續處理。
//很簡單的意思:就是根據商品id查商品詳情表,然後用查出來的ID去查商品分類表。
select
       *
       from
       commodity_classification c  
       where Classify_ID  IN(select Classify_ID from commodity_list where Good_ID='tb10025584930')

使用JOIN進行優化:

select
       *
       from
       commodity_classification c  
       left join commodity_list d on d.Classify_ID=c.Classify_ID
       where d.Good_ID='tb10025584930'

(3)使用聯合(UNION)來代替手動建立的臨時表

UNION是會把結果排序的!!!

union查詢,它可以把需要使用臨時表的兩條或更多的select查詢合併的一個查詢中(即把兩次或多次查詢結果合併起來。)。在客戶端的查詢會話結束的時候,臨時表會被自動刪除,從而保證資料庫整齊、高效。使用union來建立查詢的時候,我們只需要用UNION作為關鍵字把多個select語句連線起來就可以了,要注意的是所有select語句中的欄位數目要想同。

#

要求:兩次查詢的列數必須一致

推薦:列的型別可以不一樣,但推薦查詢的每一列,想對應的型別以一樣

可以來自多張表的資料:多次sql語句取出的列名可以不一致,此時以第一個sql語句的列名為準。

如果不同的語句中取出的行,有完全相同(這裡表示的是每個列的值都相同),那麼union會將相同的行合併,最終只保留一行。也可以這樣理解,union會去掉重複的行。

如果不想去掉重複的行,可以使用union all。

如果子句中有order by,limit,需用括號()包起來。推薦放到所有子句之後,即對最終合併的結果來排序或篩選。

select
       Classify_ID,Good_kinds_Name
       from
       commodity_classification 
       union
select 
        Classify_ID,Classify_Description
        from 
        commodity_list

//加條件
(select
       Classify_ID,Good_kinds_Name
       from
       commodity_classification 
       order by Classify_ID)
       union
(select 
        Classify_ID,Classify_Description
        from 
        commodity_list
        order by Good_ID)

//經常操作的含義:列出所有在中國和美國的不同的僱員名
SELECT E_Name FROM Employees_China
UNION
SELECT E_Name FROM Employees_USA

注意:

1、UNION 結果集中的列名總是等於第一個 SELECT 語句中的列名

2、UNION 內部的 SELECT 語句必須擁有相同數量的列。列也必須擁有相似的資料型別。同時,每條 SELECT 語句中的列的順序必須相同

UNION ALL的作用和語法:

預設地,UNION 操作符選取不同的值。如果允許重複的值,請使用 UNION ALL。當 ALL 隨 UNION 一起使用時(即 UNION ALL),不消除重複行。

select
       Classify_ID,Good_kinds_Name
       from
       commodity_classification 
       union ALL
select 
        Classify_ID,Classify_Description
        from 
        commodity_list

不刪除重複資料

這裡寫圖片描述

(4)建立索引(下一篇將詳講)

二、查詢編寫的注意點:

(1)對查詢進行優化,要儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。(索引的注意點在下篇將詳講)

(2)應儘量避免在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:

//最好不要給資料庫留NULL,儘可能的使用 NOT NULL填充資料庫.
select id from t where num is null

備註、描述、評論之類的可以設定為 NULL,其他最好不要使用NULL。

不要以為 NULL 不需要空間,比如:char(100) 型,在欄位建立時,空間就固定了, 不管是否插入值(NULL也包含在內),都是佔用 100個字元的空間的,如果是varchar這樣的變長欄位, null 不佔用空間。

可以在num上設定預設值0,確保表中num列沒有null值,然後這樣查詢:

select id from t where num = 0

(3)in 和 not in 也要慎用,否則會導致全表掃描,如:

select id from t where num in(1,2,3)

對於連續的數值,能用 between 就不要用 in 了:

select id from t where num between 1 and 3

很多時候用 exists 代替 in 是一個好的選擇:

select num from a where num in(select num from b)
//用這個去替換
select num from a where exists(select 1 from b where num=a.num)

(4)下面的查詢也將導致全表掃描:

select id from t where name like ‘%abc%’

若要提高效率,可以考慮全文檢索。

(5)儘量使用數字型欄位,若只含數值資訊的欄位儘量不要設計為字元型,這會降低查詢和連線的效能,並會增加儲存開銷。這是因為引擎在處理查詢和連 接時會逐個比較字串中每一個字元,而對於數字型而言只需要比較一次就夠了。

(6)任何地方都不要使用 select * from t ,用具體的欄位列表代替“*”,不要返回用不到的任何欄位。

(7)儘量使用表變數來代替臨時表。如果表變數包含大量資料,請注意索引非常有限(只有主鍵索引)。

(8)在Join表的時候使用相當型別的例,並將其索引

如果你的應用程式有很多 JOIN 查詢,你應該確認兩個表中Join的欄位是被建過索引的。這樣,MySQL內部會啟動為你優化Join的SQL語句的機制。

而且,這些被用來Join的欄位,應該是相同的型別的。例如:如果你要把 DECIMAL 欄位和一個 INT 欄位Join在一起,MySQL就無法使用它們的索引。對於那些STRING型別,還需要有相同的字符集才行。(兩個表的字符集有可能不一樣)

//在state中查詢company
SELECT company_name FROM users
    LEFT JOIN companies ON (users.state = companies.state)
    WHERE users.id = $user_id"
    //兩個 state 欄位應該是被建過索引的,而且應該是相當的型別,相同的字符集

(9)查詢索引中的sql語句有很多講究,在下篇文章我們將詳細討論。

好了,MySQL優化系列(二)–查詢優化(2)(外連線、多表聯合查詢以及查詢注意點)講完了,下一篇就是我們的索引詳講了,很難又很重要關鍵,效能的關鍵點,我會繼續出這個系列文章,分享經驗給大家。歡迎在下面指出錯誤,共同學習!!你的點贊是對我最好的支援!!