1. 程式人生 > >一些mysql資料庫效能優化方法 (17/2/28整理)

一些mysql資料庫效能優化方法 (17/2/28整理)

一、MySQL 資料庫效能優化之SQL優化

優化目標

1.減少 IO 次數

IO永遠是資料庫最容易瓶頸的地方,這是由資料庫的職責所決定的,大部分資料庫操作中超過90%的時間都是 IO 操作所佔用的,減少 IO 次數是 SQL 優化中需要第一優先考慮,當然,也是收效最明顯的優化手段。

2.降低 CPU 計算

除了 IO 瓶頸之外,SQL優化中需要考慮的就是 CPU 運算量的優化了。order by, group by,distinct … 都是消耗 CPU 的大戶(這些操作基本上都是 CPU 處理記憶體中的資料比較運算)。當我們的 IO 優化做到一定階段之後,降低 CPU 計算也就成為了我們 SQL 優化的重要目標

優化方法

1.改變 SQL 執行計劃

明確優化目標之後,我們需要確定達到我們目標的方法。對於 SQL 語句來說,達到上述2個目標的方法其實只有一個,那就是改變 SQL 的執行計劃,讓他儘量“少走彎路”,儘量通過各種“捷徑”來找到我們需要的資料,以達到 “減少 IO 次數” 和 “降低 CPU 計算” 的目標

常見誤區

count(1)和count(primary_key) 優於 count(*)

很多人為了統計記錄條數,就使用 count(1) 和 count(primary_key) 而不是 count() ,他們認為這樣效能更好,其實這是一個誤區。對於有些場景,這樣做可能效能會更差,應為資料庫對 count(

) 計數操作做了一些特別的優化。

count(column) 和 count(*) 是一樣的

這個誤區甚至在很多的資深工程師或者是 DBA 中都普遍存在,很多人都會認為這是理所當然的。實際上,count(column) 和 count(*) 是一個完全不一樣的操作,所代表的意義也完全不一樣。
count(column) 是表示結果集中有多少個column欄位不為空的記錄
count(*) 是表示整個結果集有多少條記錄

select a,b from … 比 select a,b,c from … 可以讓資料庫訪問更少的資料量

這個誤區主要存在於大量的開發人員中,主要原因是對資料庫的儲存原理不是太瞭解。
實際上,大多數關係型資料庫都是按照行(row)的方式儲存,而資料存取操作都是以一個固定大小的IO單元(被稱作 block 或者 page)為單位,一般為4KB,8KB… 大多數時候,每個IO單元中儲存了多行,每行都是儲存了該行的所有欄位(lob等特殊型別欄位除外)。
所以,我們是取一個欄位還是多個欄位,實際上資料庫在表中需要訪問的資料量其實是一樣的。
當然,也有例外情況,那就是我們的這個查詢在索引中就可以完成,也就是說當只取 a,b兩個欄位的時候,不需要回表,而c這個欄位不在使用的索引中,需要回表取得其資料。在這樣的情況下,二者的IO量會有較大差異。

order by 一定需要排序操作

我們知道索引資料實際上是有序的,如果我們的需要的資料和某個索引的順序一致,而且我們的查詢又通過這個索引來執行,那麼資料庫一般會省略排序操作,而直接將資料返回,因為資料庫知道資料已經滿足我們的排序需求了。
實際上,利用索引來優化有排序需求的 SQL,是一個非常重要的優化手段
延伸閱讀:MySQL ORDER BY 的實現分析 ,MySQL 中 GROUP BY 基本實現原理 以及 MySQL DISTINCT 的基本實現原理 這3篇文章中有更為深入的分析,尤其是第一篇
執行計劃中有 filesort 就會進行磁碟檔案排序
有這個誤區其實並不能怪我們,而是因為 MySQL 開發者在用詞方面的問題。filesort 是我們在使用 explain 命令檢視一條 SQL 的執行計劃的時候可能會看到在 “Extra” 一列顯示的資訊。
實際上,只要一條 SQL 語句需要進行排序操作,都會顯示“Using filesort”,這並不表示就會有檔案排序操作。
延伸閱讀:理解 MySQL Explain 命令輸出中的filesort,我在這裡有更為詳細的介紹

基本原則

儘量少 join

MySQL 的優勢在於簡單,但這在某些方面其實也是其劣勢。MySQL 優化器效率高,但是由於其統計資訊的量有限,優化器工作過程出現偏差的可能性也就更多。對於複雜的多表 Join,一方面由於其優化器受限,再者在 Join 這方面所下的功夫還不夠,所以效能表現離 Oracle 等關係型資料庫前輩還是有一定距離。但如果是簡單的單表查詢,這一差距就會極小甚至在有些場景下要優於這些資料庫前輩。

儘量少排序

排序操作會消耗較多的 CPU 資源,所以減少排序可以在快取命中率高等 IO 能力足夠的場景下會較大影響 SQL 的響應時間。
對於MySQL來說,減少排序有多種辦法,比如:
上面誤區中提到的通過利用索引來排序的方式進行優化
減少參與排序的記錄條數
非必要不對資料進行排序
避免使用耗費資源的操作,帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會啟動SQL引擎 執行,耗費資源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要執行兩次排序

儘量避免 select *

很多人看到這一點後覺得比較難理解,上面不是在誤區中剛剛說 select 子句中欄位的多少並不會影響到讀取的資料嗎?
是的,大多數時候並不會影響到 IO 量,但是當我們還存在 order by 操作的時候,select 子句中的欄位多少會在很大程度上影響到我們的排序效率,這一點可以通過我之前一篇介紹 MySQL ORDER BY 的實現分析 的文章中有較為詳細的介紹。
此外,上面誤區中不是也說了,只是大多數時候是不會影響到 IO 量,當我們的查詢結果僅僅只需要在索引中就能找到的時候,還是會極大減少 IO 量的。

儘量用 join 代替子查詢

雖然 Join 效能並不佳,但是和 MySQL 的子查詢比起來還是有非常大的效能優勢。MySQL 的子查詢執行計劃一直存在較大的問題,雖然這個問題已經存在多年,但是到目前已經發布的所有穩定版本中都普遍存在,一直沒有太大改善。雖然官方也在很早就承認這一問題,並且承諾儘快解決,但是至少到目前為止我們還沒有看到哪一個版本較好的解決了這一問題。

儘量少 or

當 where 子句中存在多個條件以“或”並存的時候,MySQL 的優化器並沒有很好的解決其執行計劃優化問題,再加上 MySQL 特有的 SQL 與 Storage 分層架構方式,造成了其效能比較低下,很多時候使用 union all 或者是union(必要的時候)的方式來代替“or”會得到更好的效果。

儘量用 union all 代替 union

union 和 union all 的差異主要是前者需要將兩個(或者多個)結果集合並後再進行唯一性過濾操作,這就會涉及到排序,增加大量的 CPU 運算,加大資源消耗及延遲。所以當我們可以確認不可能出現重複結果集或者不在乎重複結果集的時候,儘量使用 union all 而不是 union。

儘量早過濾

這一優化策略其實最常見於索引的優化設計中(將過濾性更好的欄位放得更靠前)。
在 SQL 編寫中同樣可以使用這一原則來優化一些 Join 的 SQL。比如我們在多個表進行分頁資料查詢的時候,我們最好是能夠在一個表上先過濾好資料分好頁,然後再用分好頁的結果集與另外的表 Join,這樣可以儘可能多的減少不必要的 IO 操作,大大節省 IO 操作所消耗的時間。

避免型別轉換

這裡所說的“型別轉換”是指 where 子句中出現 column 欄位的型別和傳入的引數型別不一致的時候發生的型別轉換:
人為在column_name 上通過轉換函式進行轉換
直接導致 MySQL(實際上其他資料庫也會有同樣的問題)無法使用索引,如果非要轉換,應該在傳入的引數上進行轉換
SELECT emp.ename, emp.job FROM emp WHERE emp.empno = 7369;
不要使用:SELECT emp.ename, emp.job FROM emp WHERE emp.empno = ‘7369
由資料庫自己進行轉換
如果我們傳入的資料型別和欄位型別不一致,同時我們又沒有做任何型別轉換處理,MySQL 可能會自己對我們的資料進行型別轉換操作,也可能不進行處理而交由儲存引擎去處理,這樣一來,就會出現索引無法使用的情況而造成執行計劃問題。

優先優化高併發的 SQL,而不是執行頻率低某些“大”SQL

對於破壞性來說,高併發的 SQL 總是會比低頻率的來得大,因為高併發的 SQL 一旦出現問題,甚至不會給我們任何喘息的機會就會將系統壓跨。而對於一些雖然需要消耗大量 IO 而且響應很慢的 SQL,由於頻率低,即使遇到,最多就是讓整個系統響應慢一點,但至少可能撐一會兒,讓我們有緩衝的機會。
從全域性出發優化,而不是片面調整
SQL 優化不能是單獨針對某一個進行,而應充分考慮系統中所有的 SQL,尤其是在通過調整索引優化 SQL 的執行計劃的時候,千萬不能顧此失彼,因小失大。

儘可能對每一條執行在資料庫中的SQL進行 explain

優化 SQL,需要做到心中有數,知道 SQL 的執行計劃才能判斷是否有優化餘地,才能判斷是否存在執行計劃問題。在對資料庫中執行的 SQL 進行了一段時間的優化之後,很明顯的問題 SQL 可能已經很少了,大多都需要去發掘,這時候就需要進行大量的 explain 操作收集執行計劃,並判斷是否需要進行優化。

二、MySQL 資料庫效能優化之表結構

很多人都將 資料庫設計正規化 作為資料庫表結構設計“聖經”,認為只要按照這個正規化需求設計,就能讓設計出來的表結構足夠優化,既能保證效能優異同時還能滿足擴充套件性要求。殊不知,在N年前被奉為“聖經”的資料庫設計3正規化早就已經不完全適用了。這裡我整理了一些比較常見的資料庫表結構設計方面的優化技巧,希望對大家有用。

由於MySQL資料庫是基於行(Row)儲存的資料庫,而數據庫操作 IO 的時候是以 page(block)的方式,也就是說,如果我們每條記錄所佔用的空間量減小,就會使每個page中可存放的資料行數增大,那麼每次 IO 可訪問的行數也就增多了。反過來說,處理相同行數的資料,需要訪問的 page 就會減少,也就是 IO 操作次數降低,直接提升效能。此外,由於我們的記憶體是有限的,增加每個page中存放的資料行數,就等於增加每個記憶體塊的快取資料量,同時還會提升記憶體換中資料命中的機率,也就是快取命中率。

資料型別選擇

資料庫操作中最為耗時的操作就是 IO 處理,大部分資料庫操作 90% 以上的時間都花在了 IO 讀寫上面。所以儘可能減少 IO 讀寫量,可以在很大程度上提高資料庫操作的效能。
我們無法改變資料庫中需要儲存的資料,但是我們可以在這些資料的儲存方式方面花一些心思。下面的這些關於欄位型別的優化建議主要適用於記錄條數較多,資料量較大的場景,因為精細化的資料型別設定可能帶來維護成本的提高,過度優化也可能會帶來其他的問題:

數字型別

非萬不得已不要使用DOUBLE,不僅僅只是儲存長度的問題,同時還會存在精確性的問題。同樣,固定精度的小數,也不建議使用DECIMAL,建議乘以固定倍數轉換成整數儲存,可以大大節省儲存空間,且不會帶來任何附加維護成本。對於整數的儲存,在資料量較大的情況下,建議區分開 TINYINT / INT / BIGINT 的選擇,因為三者所佔用的儲存空間也有很大的差別,能確定不會使用負數的欄位,建議新增unsigned定義。當然,如果資料量較小的資料庫,也可以不用嚴格區分三個整數型別。

字元型別:

非萬不得已不要使用 TEXT 資料型別,其處理方式決定了他的效能要低於char或者是varchar型別的處理。定長欄位,建議使用 CHAR 型別,不定長欄位儘量使用 VARCHAR,且僅僅設定適當的最大長度,而不是非常隨意的給一個很大的最大長度限定,因為不同的長度範圍,MySQL也會有不一樣的儲存處理。

時間型別:

儘量使用TIMESTAMP型別,因為其儲存空間只需要 DATETIME 型別的一半。對於只需要精確到某一天的資料型別,建議使用DATE型別,因為他的儲存空間只需要3個位元組,比TIMESTAMP還少。不建議通過INT型別類儲存一個unix timestamp 的值,因為這太不直觀,會給維護帶來不必要的麻煩,同時還不會帶來任何好處。

ENUM & SET:

對於狀態欄位,可以嘗試使用 ENUM 來存放,因為可以極大的降低儲存空間,而且即使需要增加新的型別,只要增加於末尾,修改結構也不需要重建表資料。如果是存放可預先定義的屬性資料呢?可以嘗試使用SET型別,即使存在多種屬性,同樣可以遊刃有餘,同時還可以節省不小的儲存空間。

LOB型別:

強烈反對在資料庫中存放 LOB 型別資料,雖然資料庫提供了這樣的功能,但這不是他所擅長的,我們更應該讓合適的工具做他擅長的事情,才能將其發揮到極致。在資料庫中儲存 LOB 資料就像讓一個多年前在學校學過一點Java的營銷專業人員來寫 Java 程式碼一樣。

字元編碼

字符集直接決定了資料在MySQL中的儲存編碼方式,由於同樣的內容使用不同字符集表示所佔用的空間大小會有較大的差異,所以通過使用合適的字符集,可以幫助我們儘可能減少資料量,進而減少IO操作次數。
純拉丁字元能表示的內容,沒必要選擇 latin1 之外的其他字元編碼,因為這會節省大量的儲存空間
如果我們可以確定不需要存放多種語言,就沒必要非得使用UTF8或者其他UNICODE字元型別,這回造成大量的儲存空間浪費
MySQL的資料型別可以精確到欄位,所以當我們需要大型資料庫中存放多位元組資料的時候,可以通過對不同表不同欄位使用不同的資料型別來較大程度減小資料儲存量,進而降低 IO 操作次數並提高快取命中率

適當拆分

有些時候,我們可能會希望將一個完整的物件對應於一張資料庫表,這對於應用程式開發來說是很有好的,但是有些時候可能會在效能上帶來較大的問題。
當我們的表中存在類似於 T**EXT 或者是很大的 VARCHAR型別的大欄位的時候,如果我們大部分訪問這張表的時候都不需要這個欄位,我們就該義無反顧的將其拆分到另外的獨立表中,以減少常用資料所佔用的儲存空間。這樣做的一個明顯好處就是每個資料塊中可以儲存的資料條數可以大大增加,既減少物理 IO 次數,也能大大提高記憶體中的快取命中率。**

上面幾點的優化都是為了減少每條記錄的儲存空間大小,讓每個資料庫中能夠儲存更多的記錄條數,以達到減少 IO 操作次數,提高快取命中率。下面這個優化建議可能很多開發人員都會覺得不太理解,因為這是典型的反正規化設計,而且也和上面的幾點優化建議的目標相違背。

適度冗餘

為什麼我們要冗餘?這不是增加了每條資料的大小,減少了每個資料塊可存放記錄條數嗎?
確實,這樣做是會增大每條記錄的大小,降低每條記錄中可存放資料的條數,但是在有些場景下我們仍然還是不得不這樣做:

被頻繁引用且只能通過 Join 2張(或者更多)大表的方式才能得到的獨立小欄位
這樣的場景由於每次Join僅僅只是為了取得某個小欄位的值,Join到的記錄又大,會造成大量不必要的 IO,完全可以通過空間換取時間的方式來優化。不過,冗餘的同時需要確保資料的一致性不會遭到破壞,確保更新的同時冗餘欄位也被更新

儘量使用 NOT NULL

NULL 型別比較特殊,SQL 難優化。雖然 MySQL NULL型別和 Oracle 的NULL 有差異,會進入索引中,但如果是一個組合索引,那麼這個NULL 型別的欄位會極大影響整個索引的效率。此外,NULL 在索引中的處理也是特殊的,也會佔用額外的存放空間。
很多人覺得 NULL 會節省一些空間,所以儘量讓NULL來達到節省IO的目的,但是大部分時候這會適得其反,雖然空間上可能確實有一定節省,倒是帶來了很多其他的優化問題,不但沒有將IO量省下來,反而加大了SQL的IO量。所以儘量確保 DEFAULT 值不是 NULL,也是一個很好的表結構設計優化習慣。

三、MySQL 資料庫效能優化之索引優化

大家都知道索引對於資料訪問的效能有非常關鍵的作用,都知道索引可以提高資料訪問效率。

為什麼索引能提高資料訪問效能?他會不會有“副作用”?是不是索引建立越多,效能就越好?到底該如何設計索引,才能最大限度的發揮其效能?

這篇文章主要是帶著上面這幾個問題來做一個簡要的分析,同時排除了業務場景所帶來的特殊性,請不要糾結業務場景的影響。

索引為什麼能提高資料訪問效能?

很多人只知道索引能夠提高資料庫的效能,但並不是特別瞭解其原理,其實我們可以用一個生活中的示例來理解。
我們讓一位不太懂計算機的朋友去圖書館確認一本叫做《MySQL效能調優與架構設計》的書是否在藏,這樣對他說:“請幫我借一本計算機類的資料庫書籍,是屬於 MySQL 資料庫範疇的,叫做《MySQL效能調優與架構設計》”。朋友會根據所屬類別,前往存放“計算機”書籍區域的書架,然後再尋找“資料庫”類存放位置,再找到一堆講述“MySQL”的書籍,最後可能發現目標在藏(也可能已經借出不在書架上)。

在這個過程中: “計算機”->“資料庫”->“MySQL”->“在藏”->《MySQL效能調優與架構設計》其實就是一個“根據索引查詢資料”的典型案例,“計算機”->“資料庫”->“MySQL”->“在藏” 就是朋友查詢書籍的索引。

假設沒有這個索引,那查詢這本書的過程會變成怎樣呢?朋友只能從圖書館入口一個書架一個書架的“遍歷”,直到找到《MySQL效能調優與架構設計》這本書為止。如果幸運,可能在第一個書架就找到。但如果不幸呢,那就慘了,可能要將整個圖書館所有的書架都找一遍才能找到我們想要的這本書。

注:這個例子中的“索引”是記錄在朋友大腦中的,實際上,每個圖書館都會有一個非常全的實際存在的索引系統(大多位於入口顯眼處),由很多個貼上了明顯標籤的小抽屜構成。這個索引系統中存放這非常齊全詳盡的索引資料,標識出我們需要查詢的“目標”在某個區域的某個書架上。而且每當有新的書籍入庫,舊的書籍銷燬以及書記資訊修改,都需要對索引系統進行及時的修正。

下面我們通過上面這個生活中的小示例,來分析一下索引,看看能的出哪些結論?

索引有哪些“副作用”?

圖書的變更(增,刪,改)都需要修訂索引,索引存在額外的維護成本
查詢翻閱索引系統需要消耗時間,索引存在額外的訪問成本
這個索引系統需要一個地方來存放,索引存在額外的空間成本

索引是不是越多越好?

如果我們的這個圖書館只是一個進出中轉站,裡面的新書進來後很快就會轉發去其他圖書館而從這個館藏中“清除”,那我們的索引就只會不斷的修改,而很少會被用來查詢圖書
所以,對於類似於這樣的存在非常大更新量的資料,索引的維護成本會非常高,如果其檢索需求很少,而且對檢索效率並沒有非常高的要求的時候,我們並不建議建立索引,或者是儘量減少索引。
如果我們的書籍量少到只有幾本或者就只有一個書架,索引並不會帶來什麼作用,甚至可能還會浪費一些查詢索引所花費的時間。
所以,對於資料量極小到通過索引檢索還不如直接遍歷來得快的資料,也並不適合使用索引。
如果我們的圖書館只有一個10平方的面積,現在連放書架都已經非常擁擠,而且館藏還在不斷增加,我們還能考慮建立索引嗎?
所以,當我們連儲存基礎資料的空間都捉襟見肘的時候,我們也應該儘量減少低效或者是去除索引。

索引該如何設計才高效?

如果我們僅僅只是這樣告訴對方的:“幫我確認一本資料庫類別的講述 MySQL 的叫做《MySQL效能調優與架構設計》的書是否在藏”,結果又會如何呢?朋友只能一個大類區域一個大類區域的去尋找“資料庫”類別,然後再找到 “MySQL”範疇,再看到我們所需是否在藏。由於我們少說了一個“計算機類”,朋友就必須到每一個大類去尋找。
所以,我們應該儘量讓查詢條件儘可能多的在索引中,儘可能通過索引完成所有過濾,回表只是取出額外的資料欄位。
如果我們是這樣說的:“幫我確認一本講述 MySQL 的資料庫範疇的計算機叢書,叫做《MySQL效能調優與架構設計》,看是否在藏”。如果這位朋友並不知道計算機是一個大類,也不知道資料庫屬於計算機大類,那這位朋友就悲劇了。首先他得遍歷每個類別確認“MySQL”存在於哪些類別中,然後從包含 “MySQL” 書籍中再看有哪些是“資料庫”範疇的(有可能部分是講述PHP或者其他開發語言的),然後再排除非計算機類的(雖然可能並沒有必要),然後才能確認。
所以,欄位的順序對組合索引效率有至關重要的作用,過濾效果越好的欄位需要更靠前。
如果我們還有這樣一個需求(雖然基本不可能):“幫我將圖書館中所有的計算機圖書借來”。朋友如果通過索引來找,每次都到索引櫃找到計算機書籍所在的區域,然後從書架上搬下一格(假設只能以一格為單位從書架上取下,類比資料庫中以block/page為單位讀取),取出第一本,然後再從索引櫃找到計算機圖書所在區域,再搬下一格,取出一本… 如此往復直至取完所有的書。如果他不通過索引來找又會怎樣呢?他需要從地一個書架一直往後找,當找到計算機的書,搬下一格,取出所有計算機的書,再往後,直至所有書架全部看一遍。在這個過程中,如果計算機類書籍較多,通過索引來取所花費的時間很可能要大於直接遍歷,因為不斷往復的索引翻閱所消耗的時間會非常長。(延伸閱讀:這裡有一篇以前寫的關於Oracle的文章,索引掃描還是全表掃描(Index Scan Or Full Table Scan))
所以,當我們需要讀取的資料量佔整個資料量的比例較大抑或者說索引的過濾效果並不是太好的時候,使用索引並不一定優於全表掃描。
如果我們的朋友不知道“資料庫”這個類別可以屬於“計算機”這個大類,抑或者圖書館的索引系統中這兩個類別屬性並沒有關聯關係,又會怎樣呢?也就是說,朋友得到的是2個獨立的索引,一個是告知“計算機”這個大類所在的區域,一個是“資料庫”這個小類所在的區域(很可能是多個區域),那麼他只能二者選其一來搜尋我的需求。即使朋友可以分別通過2個索引檢索然後自己在腦中取交集再找,那這樣的效率實際過程中也會比較低下。
所以,在實際使用過程中,一次資料訪問一般只能利用到1個索引,這一點在索引建立過程中一定要注意,不是說一條SQL語句中Where子句裡面每個條件都有索引能對應上就可以了。
最後總結一下法則:不要在建立的索引的資料列上進行下列操作:
◆避免對索引欄位進行計算操作

◆避免在索引欄位上使用not,<>,!=

◆避免在索引列上使用IS NULL和IS NOT NULL

◆避免在索引列上出現數據型別轉換

◆避免在索引欄位上使用函式

◆避免建立索引的列中使用空值。

四、MySQL 資料庫效能優化之快取引數優化

資料庫屬於 IO 密集型的應用程式,其主要職責就是資料的管理及儲存工作。而我們知道,從記憶體中讀取一個數據庫的時間是微秒級別,而從一塊普通硬碟上讀取一個IO是在毫秒級別,二者相差3個數量級。所以,要優化資料庫,首先第一步需要優化的就是 IO,儘可能將磁碟IO轉化為記憶體IO。本文先從 MySQL 資料庫IO相關引數(快取引數)的角度來看看可以通過哪些引數進行IO優化:

query_cache_size/query_cache_type (global)
Query cache 作用於整個 MySQL Instance,主要用來快取 MySQL 中的 ResultSet,也就是一條SQL語句執行的結果集,所以僅僅只能針對select語句。當我們打開了 Query Cache 功能,MySQL在接受到一條select語句的請求後,如果該語句滿足Query Cache的要求(未顯式說明不允許使用Query Cache,或者已經顯式申明需要使用Query Cache),MySQL 會直接根據預先設定好的HASH演算法將接受到的select語句以字串方式進行hash,然後到Query Cache 中直接查詢是否已經快取。也就是說,如果已經在快取中,該select請求就會直接將資料返回,從而省略了後面所有的步驟(如 SQL語句的解析,優化器優化以及向儲存引擎請求資料等),極大的提高效能

當然,Query Cache 也有一個致命的缺陷,那就是當某個表的資料有任何任何變化,都會導致所有引用了該表的select語句在Query Cache 中的快取資料失效。所以,當我們的資料變化非常頻繁的情況下,使用Query Cache 可能會得不償失。

Query Cache的使用需要多個引數配合,其中最為關鍵的是 query_cache_size 和 query_cache_type ,前者設定用於快取 ResultSet 的記憶體大小,後者設定在何場景下使用 Query Cache。在以往的經驗來看,如果不是用來快取基本不變的資料的MySQL資料庫,query_cache_size 一般 256MB 是一個比較合適的大小。當然,這可以通過計算Query Cache的命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))來進行調整。query_cache_type可以設定為0(OFF),1(ON)或者2(DEMOND),分別表示完全不使用query cache,除顯式要求不使用query cache(使用sql_no_cache)之外的所有的select都使用query cache,只有顯示要求才使用query cache(使用sql_cache)。

binlog_cache_size (global)
Binlog Cache 用於在打開了二進位制日誌(binlog)記錄功能的環境,是 MySQL 用來提高binlog的記錄效率而設計的一個用於短時間內臨時快取binlog資料的記憶體區域。

一般來說,如果我們的資料庫中沒有什麼大事務,寫入也不是特別頻繁,2MB~4MB是一個合適的選擇。但是如果我們的資料庫大事務較多,寫入量比較大,可與適當調高binlog_cache_size。同時,我們可以通過binlog_cache_use 以及 binlog_cache_disk_use來分析設定的binlog_cache_size是否足夠,是否有大量的binlog_cache由於記憶體大小不夠而使用臨時檔案(binlog_cache_disk_use)來快取了。

key_buffer_size (global)
Key Buffer 可能是大家最為熟悉的一個 MySQL 快取引數了,尤其是在 MySQL 沒有更換預設儲存引擎的時候,很多朋友可能會發現,預設的 MySQL 配置檔案中設定最大的一個記憶體引數就是這個引數了。key_buffer_size 引數用來設定用於快取 MyISAM儲存引擎中索引檔案的記憶體區域大小。如果我們有足夠的記憶體,這個快取區域最好是能夠存放下我們所有的 MyISAM 引擎表的所有索引,以儘可能提高效能。

此外,當我們在使用MyISAM 儲存的時候有一個及其重要的點需要注意,由於 MyISAM 引擎的特性限制了他僅僅只會快取索引塊到記憶體中,而不會快取表資料庫塊。所以,我們的 SQL 一定要儘可能讓過濾條件都在索引中,以便讓快取幫助我們提高查詢效率。

bulk_insert_buffer_size (thread)
和key_buffer_size一樣,這個引數同樣也僅作用於使用 MyISAM儲存引擎,用來快取批量插入資料的時候臨時快取寫入資料。當我們使用如下幾種資料寫入語句的時候,會使用這個記憶體區域來快取批量結構的資料以幫助批量寫入資料檔案:

insert … select …
insert … values (…) ,(…),(…)…
load data infile… into… (非空表)

innodb_buffer_pool_size(global)
當我們使用InnoDB儲存引擎的時候,innodb_buffer_pool_size 引數可能是影響我們效能的最為關鍵的一個引數了,他用來設定用於快取 InnoDB 索引及資料塊的記憶體區域大小,類似於 MyISAM 儲存引擎的 key_buffer_size 引數,當然,可能更像是 Oracle 的 db_cache_size。簡單來說,當我們操作一個 InnoDB 表的時候,返回的所有資料或者去資料過程中用到的任何一個索引塊,都會在這個記憶體區域中走一遭。

和key_buffer_size 對於 MyISAM 引擎一樣,innodb_buffer_pool_size 設定了 InnoDB 儲存引擎需求最大的一塊記憶體區域的大小,直接關係到 InnoDB儲存引擎的效能,所以如果我們有足夠的記憶體,儘可將該引數設定到足夠打,將盡可能多的 InnoDB 的索引及資料都放入到該快取區域中,直至全部。

我們可以通過 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100% 計算快取命中率,並根據命中率來調整 innodb_buffer_pool_size 引數大小進行優化。

innodb_additional_mem_pool_size(global)
這個引數我們平時調整的可能不是太多,很多人都使用了預設值,可能很多人都不是太熟悉這個引數的作用。innodb_additional_mem_pool_size 設定了InnoDB儲存引擎用來存放資料字典資訊以及一些內部資料結構的記憶體空間大小,所以當我們一個MySQL Instance中的資料庫物件非常多的時候,是需要適當調整該引數的大小以確保所有資料都能存放在記憶體中提高訪問效率的。

這個引數大小是否足夠還是比較容易知道的,因為當過小的時候,MySQL 會記錄 Warning 資訊到資料庫的 error log 中,這時候你就知道該調整這個引數大小了。

innodb_log_buffer_size (global)
這是 InnoDB 儲存引擎的事務日誌所使用的緩衝區。類似於 Binlog Buffer,InnoDB 在寫事務日誌的時候,為了提高效能,也是先將資訊寫入 Innofb Log Buffer 中,當滿足 innodb_flush_log_trx_commit 引數所設定的相應條件(或者日誌緩衝區寫滿)之後,才會將日誌寫到檔案(或者同步到磁碟)中。可以通過 innodb_log_buffer_size 引數設定其可以使用的最大記憶體空間。
注:innodb_flush_log_trx_commit 引數對 InnoDB Log 的寫入效能有非常關鍵的影響。該引數可以設定為0,1,2,解釋如下:

0:log buffer中的資料將以每秒一次的頻率寫入到log file中,且同時會進行檔案系統到磁碟的同步操作,但是每個事務的commit並不會觸發任何log buffer 到log file的重新整理或者檔案系統到磁碟的重新整理操作;
1:在每次事務提交的時候將log buffer 中的資料都會寫入到log file,同時也會觸發檔案系統到磁碟的同步;
2:事務提交會觸發log buffer 到log file的重新整理,但並不會觸發磁碟檔案系統到磁碟的同步。此外,每秒會有一次檔案系統到磁碟同步操作。

此外,MySQL文件中還提到,這幾種設定中的每秒同步一次的機制,可能並不會完全確保非常準確的每秒就一定會發生同步,還取決於程序排程的問題。實際上,InnoDB 能否真正滿足此引數所設定值代表的意義正常 Recovery 還是受到了不同 OS 下檔案系統以及磁碟本身的限制,可能有些時候在並沒有真正完成磁碟同步的情況下也會告訴 mysqld 已經完成了磁碟同步。

innodb_max_dirty_pages_pct (global)
這個引數和上面的各個引數不同,他不是用來設定用於快取某種資料的記憶體大小的一個引數,而是用來控制在 InnoDB Buffer Pool 中可以不用寫入資料檔案中的Dirty Page 的比例(已經被修但還沒有從記憶體中寫入到資料檔案的髒資料)。這個比例值越大,從記憶體到磁碟的寫入操作就會相對減少,所以能夠一定程度下減少寫入操作的磁碟IO。

但是,如果這個比例值過大,當資料庫 Crash 之後重啟的時間可能就會很長,因為會有大量的事務資料需要從日誌檔案恢復出來寫入資料檔案中。同時,過大的比例值同時可能也會造成在達到比例設定上限後的 flush 操作“過猛”而導致效能波動很大。

上面這幾個引數是 MySQL 中為了減少磁碟物理IO而設計的主要引數,對 MySQL 的效能起到了至關重要的作用。

—EOF—

按照 mcsrainbow 朋友的要求,這裡列一下根據以往經驗得到的相關引數的建議值:

query_cache_type : 如果全部使用innodb儲存引擎,建議為0,如果使用MyISAM 儲存引擎,建議為2,同時在SQL語句中顯式控制是否是喲你gquery cache

query_cache_size: 根據 命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))進行調整,一般不建議太大,256MB可能已經差不多了,大型的配置型靜態資料可適當調大

binlog_cache_size: 一般環境2MB~4MB是一個合適的選擇,事務較大且寫入頻繁的資料庫環境可以適當調大,但不建議超過32MB

key_buffer_size: 如果不使用MyISAM儲存引擎,16MB足以,用來快取一些系統表資訊等。如果使用 MyISAM儲存引擎,在記憶體允許的情況下,儘可能將所有索引放入記憶體,簡單來說就是“越大越好”

bulk_insert_buffer_size: 如果經常性的需要使用批量插入的特殊語句(上面有說明)來插入資料,可以適當調大該引數至16MB~32MB,不建議繼續增大,某人8MB

innodb_buffer_pool_size: 如果不使用InnoDB儲存引擎,可以不用調整這個引數,如果需要使用,在記憶體允許的情況下,儘可能將所有的InnoDB資料檔案存放如記憶體中,同樣將但來說也是“越大越好”
innodb_additional_mem_pool_size: 一般的資料庫建議調整到8MB~16MB,如果表特別多,可以調整到32MB,可以根據error log中的資訊判斷是否需要增大

innodb_log_buffer_size: 預設是1MB,系的如頻繁的系統可適當增大至4MB~8MB。當然如上面介紹所說,這個引數實際上還和另外的flush引數相關。一般來說不建議超過32MB

innodb_max_dirty_pages_pct: 根據以往的經驗,重啟恢復的資料如果要超過1GB的話,啟動速度會比較慢,幾乎難以接受,所以建議不大於 1GB/innodb_buffer_pool_size(GB)*100 這個值。當然,如果你能夠忍受啟動時間比較長,而且希望儘量減少記憶體至磁碟的flush,可以將這個值調整到90,但不建議超過90