MySQL單表查詢優化技巧

本文的原文地址在此:https://www.percona.com/blog/2015/04/27/indexing-101-optimizing-mysql-queries-on-a-single-table/,以下是譯文。

-----------------------------------------------------------這是一條分割線-----------------------------------------------------------

我最近碰到了很多性能很糟糕的MySQL單表查詢。原因很簡單:索引創建得不正確,導致執行計劃的性能低下。下面是一些能幫助你優化單表查詢性能的要點。

免責聲明:我會給出一些要點,但並不打算包含所有的可能情況。我100%相信你能夠找到我的要點不適應的案例,但是我也相信大部分情況下,我寫的這些要點會幫助到你。爲了簡單起見,我也不會討論一些MySQL 5.6+版本的一些新特性,如Index Condition Pushdown。注意這些新特性會對響應時間有極大的影響(縮短或延長均有可能)。

索引能做什麼?

索引主要做3件事:過濾(filter),排序或分組(sort/group),覆蓋(cover)。前兩個沒什麼好說的,但並不是每個人都知道什麼叫“覆蓋索引”。事實上這是個很簡單的東西。

一個基本查詢的工作流如下:

1. 使用索引以查找匹配的記錄,並得到數據的指針。

2. 使用相關數據的指針。

3. 返回查詢到的記錄。

當可以使用覆蓋索引時,索引將會覆蓋查詢中的所有字段,因此第二步將會被跳過,於是查詢流程就變成了下面這樣:

1. 使用索引以查找匹配的記錄

2. 返回查詢到的記錄。

大部分情況下,索引都比較小,可以加載在內存中,而數據很大,無法全部存放在內存裏:當使用覆蓋索引時,可以避免很多的磁盤操作,因此對性能也會有極大的改善。

下面讓我們來看一些常見的查詢案例。

單個等於查詢(Single equality)

這是最基本的情景:

SELECT * FROM t WHERE c = 100

毫無疑問這種情況下,要給c字段創建索引。要注意的是,如果查詢條件不夠精確(if the criteria is not selective enough,這句話我不理解),優化器很可能會選擇全表查詢,因爲這樣有可能性能更好。

這種單個等於查詢也包括只查詢部分字段,而不是所有字段,如:

SELECT c1, c2 FROM t WHERE c = 100
這裏應該創建一個(c,c1,c2)的索引,因爲這樣是覆蓋索引。注意不是創建(c1,c2,c)!這同樣也是覆蓋索引,但是對過濾沒什麼幫助(記住MySQL索引的最左原則)。

多個等於查詢(Multiple equalities)

SELECT * FROM t WHERE c = 100 and d = 'xyz'
這種情況也很容易優化:創建索引(c,d)或(d,c)。

最常見的錯誤是建立兩個索引:一個是c,一個是d。儘管MySQL根據index_merge算法能同時使用這兩個索引,但這樣依然是糟糕的選擇(詳情參見以下幾篇文章:https://www.percona.com/blog/2009/09/19/multi-column-indexes-vs-index-merge/https://www.percona.com/blog/2012/12/14/the-optimization-that-often-isnt-index-merge-intersection/https://www.percona.com/blog/2014/01/03/multiple-column-index-vs-multiple-indexes-with-mysql-56/

等於與不等於並存的查詢(Equality and inequality)

SELECT * FROM t WHERE c > 100 and d = 'xyz'

這種情況我們必須要小心,因爲只要有一列使用了不等於計算,那麼它將阻止其他列使用索引。

因此我們需要創建一個(d,c)的索引,這時候c和d兩個條件都會走索引,這也是我們想要的結果。

而如果我們創建的是(c,d)索引,則只有c列的索引會被利用,這樣效率會比較低。

因此,索引中字段的順序對於這種等於/不等於並存的查詢有極大的影響。

多個不等於查詢(Multiple inequalities)

SELECT * FROM t WHERE c > 100 and b < 10 and d = 'xyz'

這裏有兩個不等於,前面已經說了不等於會終止索引查詢,因此我們不可能做到b、c、d都被索引覆蓋(註釋1)。因此我們必須要做出決定,到底是創建索引(d,b)還是索引(d,c)?

在不知道表裏具體數據的情況下,創建上面任何一種都無所謂,最關鍵的是,一定要把等於條件(在這裏是d)所在列,放在索引的最左側。

註釋1事實上還是有一種“曲線救國”的方法,能同時滿足所有條件,即按照字段b分區(partition on b),然後創建索引(d,c),或按照字段c分區(partition onc),然後創建索引(d,b)。這個的細節已經超出了本文的討論範圍,不過這也是這種情況下的一種解決方法。

多個等於與排序(Equalities and sort)

SELECT * FROM t WHERE c = 100 and d = 'xyz' ORDER BY b
就像第一節中寫的那樣,索引可以過濾、排序,因此這個查詢很容易優化。不過和不等於類似,我們對於索引中字段的順序必須足夠小心:要求是先過濾後排序

根據上面“先過濾後排序”的要求可知,(c,d,b)或(d,c,b)是不錯的選擇;而(b,c,d)或(b,d,c)則比較糟糕,因爲他們只排序,不過濾。

如果是下面這種情況:

SELECT c1, c2 FROM t WHERE c = 100 and d = 'xyz' ORDER BY b
我們可以創建一個集過濾、排序、覆蓋於一體的索引:(c,d,b,c1,c2)。

不等於與排序(Inequality and sort)

常見的情況有2種。下面是情況一(不等於、等於、排序都有):

SELECT * FROM t WHERE c > 100 and d = 'xyz' ORDER BY b
這種情況有兩種思路:(d,b)或(d,c)。至於哪種效率更高,這取決於你的數據,需要具體情況具體分析。

情況二如下(只有不等於和排序):

SELECT * FROM t WHERE c > 100 ORDER BY b
這種情況沒有等於條件,因此b和c只能選一種,具體選哪一種同樣和你的數據有關。通常情況下,選過濾的會多一些(即c字段)。

總結

本文並沒有包含所有的情況,但同樣指出了一些你必須要小心的地方。今後,我會列舉一個看起來十分複雜的例子,不過只要你把這篇文章看懂了,它其實很簡單。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章