MySQL的10件事—它們也許和你預想的不同

MySQL的10件事—它們也許和你預想的不同

今天咱們要說的是關於MySQL的十件事,它們當中不少技術細節跟你想象的不徹底同樣。

    【51CTO經典譯文】#10. 搜索一個「NULL」值 架構

       
    1. SELECT  *  
    2. FROM    a  
    3. WHERE   a.column = NULL 

    在SQL中,NULL什麼也不等於,並且NULL也不等於NULL。這個查詢不會返回任何結果的,實際上,當構建那個plan的時候,優化器會把這樣的語句優化掉。函數

    當搜索NULL值的時候,應該使用這樣的查詢:優化

       
    1. SELECT  *  
    2. FROM    a  
    3. WHERE   a.column IS NULL 

    #9. 使用附加條件的LEFT JOIN雲計算

       
    1. SELECT  *  
    2. FROM    a  
    3. LEFT JOIN 
    4.         b  
    5. ON      b.a = a.id  
    6. WHERE   b.column = 'something' 

    除了從a返回每一個記錄(至少一次),當沒有真正匹配的記錄的時候,用NULL值代替缺失的字段以外,LEFT JOIN和INNER JOIN都是同樣的。spa

    可是,在LEFT JOIN以後纔會檢查WHERE條件,因此,上面這個查詢在鏈接以後纔會檢查column。就像咱們剛纔瞭解到的那樣,非NULL值才能夠知足相等條件,因此,在a的記錄中,那些在b中沒有對應的條目的記錄不可避免地要被過濾掉。code

    從本質上來講,這個查詢是一個INNER JOIN,只是效率要低一些。排序

    爲了真正地匹配知足b.column = 'something'條件的記錄(這時要返回a中的所有記錄,也就是說,不過濾掉那些在b中沒有對應的條目的記錄),這個條件應該放在ON子句中:索引

       
    1. SELECT  *  
    2. FROM    a  
    3. LEFT JOIN 
    4.         b  
    5. ON      b.a = a.id  
    6.         AND b.column = 'something' 

    #8. 小於一個值,可是不爲NULL文檔

    我常常看到這樣的查詢:

       
    1. SELECT  *  
    2. FROM    b  
    3. WHERE   b.column < 'something' 
    4.        AND b.column IS NOT NULL 

    實際上,這並非一個錯誤:這個查詢是有效的,是故意這樣作的。可是,這裏的IS NOT NULL是冗餘的。

    若是b.column是NULL,那麼沒法知足b.column < 'something'這個條件,由於任何一個和NULL進行的比較都會被斷定爲布爾NULL,是不會經過過濾器的。

    有趣的是,這個附加的NULL檢查不能和「大於」查詢(例如:b.column > 'something')一塊兒使用。

    這是由於,在MySQL中,在ORDER BY的時候,NULL會排在前面,所以,一些人錯誤地認爲NULL比任何其餘的值都要小。

    這個查詢能夠被簡化:

       
    1. SELECT  *  
    2. FROM    b  
    3. WHERE   b.column < 'something' 

    在b.column中,不可能返回NULL

    #7. 按照NULL來進行鏈接

       
    1. SELECT  *  
    2. FROM    a  
    3. JOIN    b  
    4. ON      a.column = b.column 

    在兩個表中,當column是nullable的時候,這個查詢不會返回兩個字段都是NULL的記錄,緣由如上所述:兩個NULL並不相等。

    這個查詢應該這樣來寫:

       
    1. SELECT  *  
    2. FROM    a  
    3. JOIN    b  
    4. ON      a.column = b.column 
    5.         OR (a.column IS NULL AND b.column IS NULL

    MySQL的優化器會把這個查詢當成一個「等值鏈接」,而後提供一個特殊的鏈接條件:ref_or_null

    #6. NOT IN和NULL值

       
    1. SELECT  a.*  
    2. FROM    a  
    3. WHERE   a.column NOT IN 
    4.         (  
    5.         SELECT column 
    6.         FROM    b  
    7.         ) 

    若是在b.column中有一個NULL值,那麼這個查詢是不會返回任何結果的。和其餘謂詞同樣,IN  和 NOT IN 遇到NULL也會被斷定爲NULL。

    你應該使用NOT EXISTS重寫這個查詢:

       
    1. SELECT  a.*  
    2. FROM    a  
    3. WHERE   NOT EXISTS  
    4.         (  
    5.         SELECT NULL 
    6.         FROM    b  
    7.        WHERE   b.column = a.column 
    8.        ) 

    不像IN,EXISTS老是被斷定爲true或false的。

    #5. 對隨機的樣本進行排序

       
    1. SELECT  *  
    2. FROM    a  
    3. ORDER BY 
    4.         RAND(), column 
    5. LIMIT 10 

    這個查詢試圖選出10個隨機的記錄,按照column來排序。

    ORDER BY會按照天然順序來對輸出結果進行排序:這就是說,當第一個表達式的值相等的時候,這些記錄纔會按照第二個表達式來排序。

    可是,RAND()的結果是隨機的。要讓RAND()的值相等是行不通的,因此,按照RAND()排序之後,再按照column來排序也是沒有意義的。

    要對隨機的樣本記錄進行排序,可使用這個查詢:

       
    1. SELECT  *  
    2. FROM    (  
    3.         SELECT  *  
    4.         FROM    mytable  
    5.         ORDER BY 
    6.                 RAND()  
    7.         LIMIT 10  
    8.        ) q  
    9. ORDER BY 
    10.        column 

    #4. 經過一個組來選取任意的記錄

    這個查詢打算經過某個組(定義爲grouper來)來選出一些記錄

       
    1. SELECT  DISTINCT(grouper), a.*  
    2. FROM    a 

    DISTINCT不是一個函數,它是SELECT子句的一部分。它會應用到SELECT列表中的全部列,實際上,這裏的括號是能夠省略的。因此,這個查詢可能會選出grouper中的值都相同的記錄(若是在其餘列中,至少有一個列的值是不一樣的)。

    有時,這個查詢能夠正常地使用( 這主要依賴於MySQL對GROUP BY的擴展):

       
    1. SELECT  a.*  
    2. FROM    a  
    3. GROUP BY 
    4.         grouper 

    在某個組中返回的非聚合的列能夠被任意地使用。

    首先,這彷佛是一個很好的解決方案,可是,它存在着一個很嚴重的缺陷。它依賴於這樣一個假設:雖然能夠經過組來任意地獲取,可是返回的全部值都要屬於一條記錄。

    雖然當前的實現彷佛就是這樣的,可是它並無文檔化,不管什麼時候,它都有可能被改變(尤爲是,當MySQL學會了在GROUP BY的後面使用index_union的時候)。因此依賴於這個行爲並不安全。

    若是MySQL支持分析函數的話,這個查詢能夠很容易地用另外一種更清晰的方式來重寫。可是,若是這張表擁有一個PRIMARY KEY的話,即便不使用分析函數,也能夠作到這一點:

       
    1. SELECT  a.*  
    2. FROM    (  
    3.         SELECT  DISTINCT grouper  
    4.         FROM    a  
    5.         ) ao  
    6. JOIN    a  
    7. ON      a.id =  
    8.         (  
    9.         SELECT  id  
    10.        FROM    a ai  
    11.         WHERE   ai.grouper = ao.grouper  
    12.         LIMIT 1  
    13.         ) 

    #3. 經過一個組來選取第一條記錄

    把前面那個查詢稍微變化一下:

       
    1. SELECT  a.*  
    2. FROM    a  
    3. GROUP BY 
    4.         grouper  
    5. ORDER BY 
    6.         MIN(id) DESC 

    和前面那個查詢不一樣,這個查詢試圖選出id值最小的記錄。

    一樣:沒法保證經過a.*返回的非聚合的值都屬於id值最小的那條記錄(或者任意一條記錄)

    這樣作會更清晰一些:

       
    1. SELECT  a.*  
    2. FROM    (  
    3.         SELECT  DISTINCT grouper  
    4.        FROM    a  
    5.         ) ao  
    6. JOIN    a  
    7. ON      a.id =  
    8.         (  
    9.         SELECT  id  
    10.         FROM    a ai  
    11.         WHERE   ai.grouper = ao.grouper  
    12.         ORDER BY 
    13.                 ai.grouper, ai.id  
    14.         LIMIT 1  
    15.         ) 

    這個查詢和前面那個查詢相似,可是使用額外的ORDER BY能夠確保按id來排序的第一條記錄會被返回。

    #2. IN和‘,’——值的分隔列表

    這個查詢試圖讓column的值匹配用‘,’分隔的字符串中的任意一個值:

       
    1. SELECT  *  
    2. FROM    a  
    3. WHERE   column IN ('1, 2, 3'

    這不會正常發揮做用的,由於在IN列表中,那個字符串並不會被展開。

    若是列column是一個VARCHAR,那麼它(做爲一個字符串)會和整個列表(也做爲一個字符串)進行比較,固然,這不可能匹配。若是 column是某個數值類型,那麼這個列表會被強制轉換爲那種數值類型(在最好的狀況下,只有第一項會匹配)

    處理這個查詢的正確方法應該是使用合適的IN列表來重寫它:

       
    1. SELECT  *  
    2. FROM    a  
    3. WHERE   column IN (1, 2, 3) 

    或者,也可使用內聯:

       
    1. SELECT  *  
    2. FROM    (  
    3.         SELECT  1 AS id  
    4.         UNION ALL 
    5.         SELECT  2 AS id  
    6.         UNION ALL 
    7.         SELECT  3 AS id  
    8.         ) q  
    9. JOIN    a  
    10. ON      a.column = q.id 

    可是,有時這是不可能的。

    若是不想改變那個查詢的參數,可使用FIND_IN_SET:

       
    1. SELECT  *  
    2. FROM    a  
    3. WHERE   FIND_IN_SET(column'1,2,3'

    可是,這個函數不能夠利用索引從表中檢索行,會在a上執行全表掃描。

    #1. LEFT JOIN和COUNT(*)

       
    1. SELECT  a.id, COUNT(*)  
    2. FROM    a  
    3. LEFT JOIN 
    4.         b  
    5. ON      b.a = a.id  
    6. GROUP BY 
    7.         a.id 

    這個查詢試圖統計出對於a中的每條記錄來講,在b中匹配的記錄的數目。

    問題是,在這樣一個查詢中,COUNT(*)永遠不會返回一個0。對於a中某條記錄來講,若是沒有匹配的記錄,那麼那條記錄仍是會被返回和計數。

    只有須要統計b中的記錄數目的時候才應該使用COUNT。既然可使用COUNT(*),那麼咱們也可使用一個參數來調用它(忽略掉NULL),咱們能夠把b.a傳遞給它。在這個例子中,做爲一個鏈接主鍵,它不能夠爲空,可是若是不想匹配,它也能夠爲空。

    原文標題:10 things in MySQL (that won’t work as expected)

    相關文章
    相關標籤/搜索