1. 程式人生 > >必須得告訴大家的MySQL優化原理(下)

必須得告訴大家的MySQL優化原理(下)

作者:CHEN川
連結:https://www.jianshu.com/p/d7665192aaaf
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。

特定型別查詢優化

優化COUNT()查詢

COUNT()可能是被大家誤解最多的函數了,它有兩種不同的作用,其一是統計某個列值的數量,其二是統計行數。統計列值時,要求列值是非空的它不會統計NULL。如果確認括號中的表示式不可能為空時,實際上就是在統計行數。最簡單的就是當使用COUNT(*)時,並不是我們所想象的那樣擴充套件成所有的列,實際上,它會忽略所有的列而直接統計行數。

我們最常見的誤解也就在這兒,在括號內指定了一列卻希望統計結果是行數,而且還常常誤以為前者的效能會更好。但實際並非這樣,如果要統計行數,直接使用COUNT(*)

,意義清晰,且效能更好。

有時候某些業務場景並不需要完全精確的COUNT值,可以用近似值來代替,EXPLAIN出來的行數就是一個不錯的近似值,而且執行EXPLAIN並不需要真正地去執行查詢,所以成本非常低。通常來說,執行COUNT()都需要掃描大量的行才能獲取到精確的資料,因此很難優化,MySQL層面還能做得也就只有覆蓋索引了。如果還不能解決問題,只有從架構層面解決了,比如新增彙總表,或者使用redis這樣的外部快取系統。

優化關聯查詢

大資料場景下,表與表之間通過一個冗餘欄位來關聯,要比直接使用JOIN有更好的效能。如果確實需要使用關聯查詢的情況下,需要特別注意的是:

  • 確保ON
    USING字句中的列上有索引
    。在建立索引的時候就要考慮到關聯的順序。當表A和表B用列c關聯的時候,如果優化器關聯的順序是A、B,那麼就不需要在A表的對應列上建立索引。沒有用到的索引會帶來額外的負擔,一般來說,除非有其他理由,只需要在關聯順序中的第二張表的相應列上建立索引(具體原因下文分析)。
  • 確保任何的GROUP BYORDER BY中的表示式只涉及到一個表中的列,這樣MySQL才有可能使用索引來優化。

要理解優化關聯查詢的第一個技巧,就需要理解MySQL是如何執行關聯查詢的。當前MySQL關聯執行的策略非常簡單,它對任何的關聯都執行巢狀迴圈關聯操作,即先在一個表中迴圈取出單條資料,然後在巢狀迴圈到下一個表中尋找匹配的行,依次下去,直到找到所有表中匹配的行為止。然後根據各個表匹配的行,返回查詢中需要的各個列。

太抽象了?以上面的示例來說明,比如有這樣的一個查詢:

SELECT A.xx,B.yy 
FROM A INNER JOIN B USING(c)
WHERE A.xx IN (5,6)

假設MySQL按照查詢中的關聯順序A、B來進行關聯操作,那麼可以用下面的偽程式碼表示MySQL如何完成這個查詢:

outer_iterator = SELECT A.xx,A.c FROM A WHERE A.xx IN (5,6);
outer_row = outer_iterator.next;
while(outer_row) {
    inner_iterator = SELECT B.yy FROM B WHERE B.c = outer_row.c;
    inner_row = inner_iterator.next;
    while(inner_row) {
        output[inner_row.yy,outer_row.xx];
        inner_row = inner_iterator.next;
    }
    outer_row = outer_iterator.next;
}

可以看到,最外層的查詢是根據A.xx列來查詢的,A.c上如果有索引的話,整個關聯查詢也不會使用。再看內層的查詢,很明顯B.c上如果有索引的話,能夠加速查詢,因此只需要在關聯順序中的第二張表的相應列上建立索引即可。

優化LIMIT分頁

當需要分頁操作時,通常會使用LIMIT加上偏移量的辦法實現,同時加上合適的ORDER BY字句。如果有對應的索引,通常效率會不錯,否則,MySQL需要做大量的檔案排序操作。

一個常見的問題是當偏移量非常大的時候,比如:LIMIT 10000 20這樣的查詢,MySQL需要查詢10020條記錄然後只返回20條記錄,前面的10000條都將被拋棄,這樣的代價非常高。

優化這種查詢一個最簡單的辦法就是儘可能的使用覆蓋索引掃描,而不是查詢所有的列。然後根據需要做一次關聯查詢再返回所有的列。對於偏移量很大時,這樣做的效率會提升非常大。考慮下面的查詢:

SELECT film_id,description FROM film ORDER BY title LIMIT 50,5;

如果這張表非常大,那麼這個查詢最好改成下面的樣子:

SELECT film.film_id,film.description
FROM film INNER JOIN (
    SELECT film_id FROM film ORDER BY title LIMIT 50,5
) AS tmp USING(film_id);

這裡的延遲關聯將大大提升查詢效率,讓MySQL掃描儘可能少的頁面,獲取需要訪問的記錄後在根據關聯列返回原表查詢所需要的列。

有時候如果可以使用書籤記錄上次取資料的位置,那麼下次就可以直接從該書籤記錄的位置開始掃描,這樣就可以避免使用OFFSET,比如下面的查詢:

SELECT id FROM t LIMIT 10000, 10;
改為:
SELECT id FROM t WHERE id > 10000 LIMIT 10;

其他優化的辦法還包括使用預先計算的彙總表,或者關聯到一個冗餘表,冗餘表中只包含主鍵列和需要做排序的列。

優化UNION

MySQL處理UNION的策略是先建立臨時表,然後再把各個查詢結果插入到臨時表中,最後再來做查詢。因此很多優化策略在UNION查詢中都沒有辦法很好的時候。經常需要手動將WHERELIMITORDER BY等字句“下推”到各個子查詢中,以便優化器可以充分利用這些條件先優化。

除非確實需要伺服器去重,否則就一定要使用UNION ALL,如果沒有ALL關鍵字,MySQL會給臨時表加上DISTINCT選項,這會導致整個臨時表的資料做唯一性檢查,這樣做的代價非常高。當然即使使用ALL關鍵字,MySQL總是將結果放入臨時表,然後再讀出,再返回給客戶端。雖然很多時候沒有這個必要,比如有時候可以直接把每個子查詢的結果返回給客戶端。

結語

理解查詢是如何執行以及時間都消耗在哪些地方,再加上一些優化過程的知識,可以幫助大家更好的理解MySQL,理解常見優化技巧背後的原理。希望本文中的原理、示例能夠幫助大家更好的將理論和實踐聯絡起來,更多的將理論知識運用到實踐中。

其他也沒啥說的了,給大家留兩個思考題吧,可以在腦袋裡想想答案,這也是大家經常掛在嘴邊的,但很少有人會思考為什麼?

  1. 有非常多的程式設計師在分享時都會丟擲這樣一個觀點:儘可能不要使用儲存過程,儲存過程非常不容易維護,也會增加使用成本,應該把業務邏輯放到客戶端。既然客戶端都能幹這些事,那為什麼還要儲存過程?

  2. JOIN本身也挺方便的,直接查詢就好了,為什麼還需要檢視呢?

參考資料



作者:CHEN川
連結:https://www.jianshu.com/p/d7665192aaaf
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。


作者:CHEN川
連結:https://www.jianshu.com/p/d7665192aaaf
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。