本章主要通過針對MySQL Server( mysqld)相關實現機制的分析,得到一些相應的優化建議。主要涉及MySQL的安裝以及相關參數設置的優化,但不包括mysqld之外的比如存儲引擎相關的參數優化,存
儲引擎的相關參數設置建議將主要在下一章“ 常用存儲引擎的優化” 中進行說明。
一、MySQL安裝和優化
1.選擇合適的發行版本
a.二進制發行版(包括RPM 等包裝好的特定二進制版本)
由於MySQL 開源的特性,不僅僅MySQL AB 提供了多個平台上面的多種二進制發行版本可以供大家選擇,還有不少第三方公司(或者個人)也給我們提供了不少選擇。
使用MySQL AB 提供的二進制發行版本我們可以得到哪些好處?
a) 通過非常簡單的安裝方式快速完成MySQL 的部署;
b) 安裝版本是經過比較完善的功能和性能測試的編譯版本;
c) 所使用的編譯參數更具通用性的,且比較穩定;
d) 如果購買了MySQL 的服務,將能最大程度的得到MySQL 的技術支持;
b.第三方提供的MySQL 發行版本
大多是在MySQL AB 官方提供的源代碼方面做了或多或少的針對性改動,然后再編譯而成。這些改動有些是在某些功能上面的改進,也有些是在某寫操作的性能方面的改進。還有些由各OS 廠商所提供的發行版本,則可能是在有些代碼方面針對自己的OS 做了一些相應的底層調用的調整,以使MySQL 與自己的OS 能夠更完美的結合。當然,也有一些第三方發行版本並沒有動過MySQL 一行代碼,僅僅只是在編譯參數方面做了一些相關的調整,而讓MySQL 在某些特定場景下表現更優秀。
這樣一說,聽起來好像第三方發行的MySQL 二進制版本要比MySQL AB 官方提供的二進制發行版有更大的吸引力,那么我們是否就應該選用第三方提供的二進制發行版呢?
需要進一步分析一下第三方發行版本可能存在哪些問題?
首先,由於第三方發行版本對MySQL 所做的改動,很多都是為了應對發行者自己所處的特定場景而做出來的。所以,第三方發行版本並不一定適合其他所有使用者所處的環境。
其次,由於第三方發行版本的發行者並一定都是一個足夠讓人信任的公司(或者個人),在其生成自己的發行版本之前,是否有做過足夠全面的功能和性能測試我們不得而知,在我們使用的時候是否會出現MySQL AB 官方的發行版本中並不存在的bug?
最后,如果我們購買了MySQL 的相關服務,而又使用了第三方的發行版本,當我們的系統出現問題的時候,恐怕MySQL 的支持工程師的支持工作會大打折扣,甚至可能會拒絕提供支持。
如果大家可以完全拋開以上這些可能存在隱患的顧慮,完全可以嘗試使用非MySQL AB 官方提供的二進制版本,而選用可能具有更多特性或者更高性能的發行版本了。
之前我也對網絡上各種第三方二進制分發版本做過一些測試和比較,也發現了一些比較不錯的版本,如Percona 在整合了一些比較優秀的Patch 之后的發行版本整體質量都還不錯,使用者也比較多。當然,Percona 不僅僅分發二進制版本,同時也分發整合了一些優秀Patch 的源碼包。對於希望使Percona 提供的一些Patch 的朋友,同時又希望能夠自行編譯以進一步優化和定制MySQL 的朋友,也可以下載Percona 提供的源碼包。
對於二進制分發版本的安裝,對於安裝本身來說,我們基本上沒有太多可以優化的地方,唯一可以做的就是當我們決定了選擇第三方分發版本之后,可以根據自身環境和應用特點來選擇適合我們環境的優化發行版本來安裝。
2.源碼安裝
與二進制發行版本相比,如果我們選擇了通過源代碼進行安裝,那么在安裝過程中我們能夠對MySQL所做的調整將會更多更靈活一些。因為通過源代碼編譯我們可以:
a) 針對自己的硬件平台選用合適的編譯器來優化編譯后的二進制代碼;
b) 根據不同的軟件平台環境調整相關的編譯參數;
c) 針對我們特定應用場景選擇需要什么組件不需要什么組件;
d) 根據我們的所需要存儲的數據內容選擇只安裝我們需要的字符集;
e) 同一台主機上面可以安裝多個MySQL;
f) 等等其他一些可以根據特定應用場景所作的各種調整。
在源碼安裝給我們帶來更大靈活性的同時,同樣也給我們帶來了可能引入的隱患:
a) 對編譯參數的不夠了解造成編譯參數使用不當可能使編譯出來的二進制代碼不夠穩定;
b) 對自己的應用環境把握失誤而使用的優化參數可能反而使系統性能更差;
c) 還有一個並不能稱之為隱患的小問題就是源碼編譯安裝將使安裝部署過程更為復雜,所花費的時間更長;
通過源碼安裝的最大特點就是可以讓我們自行調整編譯參數,最大程度的定制安裝結果。下面我將自己在通過源碼編譯安裝中的一些優化心得做一個簡單的介紹,希望能夠對大家有所幫助。
在通過源碼安裝的時候,最關鍵的一步就是配置編譯參數,也就是執行通過configure 命令所設定的各種編譯選項。我們可以在MySQL 源碼所在的文件夾下面通過執行執行“./configure —help”得到可以設置的所有編譯參數選項,如下:
`configure' configures this package to adapt to many kinds of systems. Usage: ./configure [OPTION]... [VAR=VALUE]... ... ... Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX ... ... For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] ... ... Program names: --program-prefix=PREFIX prepend PREFIX to installed program names ... ... System types: --build=BUILD configure for building on BUILD [guessed] ... ... Optional Features: --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) ... ... Optional Packages: --with-charset=CHARSET ... ... --without-innodb Do not include the InnoDB table handler ... ... Some influential environment variables: CC C compiler command ... ... CCASFLAGS assembler compiler flags (defaults to CFLAGS) ... ...
上面的輸出內容中很多都已經省略了,大家完全可以通過自行測試得到更為豐富的內容輸出。下面針對幾個比較重要的編譯參數做一個簡單的介紹:
“—prefix”:設定安裝路徑,默認為“/usr/local”; “—datadir”:設定MySQL 數據文件存放路徑; “—with-charset”:設定系統的默認字符集; “—with-collation”:系統默認的校驗規則; “—with-extra-charsets”:出了默認字符集之外需要編譯安裝的字符集; “—with-unix-socket-path”:設定socket 文件地址; “—with-tcp-port”:指定特定監聽端口,默認為3306; “—with-mysqld-user”:指定運行mysqld 的os 用戶,默認為mysql; “—without-query-cache”:禁用Query Cache 功能; “—without-innodb”:禁用Innodb 存儲引擎; “--with-partition”:在5.1 版本中開啟partition 支持特性; “--enable-thread-safe-client”:以線程方式編譯客戶端; “—with-pthread”:強制使用pthread 線程庫編譯; “—with-named-thread-libs”:指定使用某個特定的線程庫編譯; “—without-debug”:使用非debug 模式; “—with-mysqld-ldflags”:mysqld 的額外link 參數; “—with-client-ldflags”:client 的額外link 參數;
以上這些參數是在源碼安裝中比較常用的一些編譯參數,其中前面幾個編譯參數主要是為了方便我們在安裝的時候可以定制自己的系統,讓系統更適合我們自己應用環境的相關規范,做到環境統一,並按照實際需求生成相應的二進制代碼。而后面的一些參數主要是用來優化編譯結果的。
我想大家應該都能理解一般來說,一個系統功能越復雜,其性能一般都會越差。所以,在我們安裝編譯MySQL 的時候應該盡量只選用我們需要的組件,僅安裝我們需要的存儲引擎,僅編譯我們需要的字符集,讓我們的系統能夠盡可能的簡單,因為這樣的MySQL 也會給我們帶來盡可能高的性能。
此外,對於一些特定的軟件環境上,可能會有多種線程庫的選擇的,如果你對各個線程庫較為了解,完全可以通過編譯參數設定讓MySQL 使用最合適的線程庫,讓MySQL 在我們特定的環境中發揮他最優化的一面。
源碼包的編譯參數中默認會以Debug 模式生成二進制代碼,而Debug 模式給MySQL 帶來的性能損失是比較大的,所以當我們編譯准備安裝的產品代碼的時候,一定不要忘記使用“—without-debug”參數禁用Debug 模式。
而“—with-mysqld-ldflags”和“—with-client-ldflags”兩個編譯參數如果設置為“-allstatic”的話,可以告訴編譯器以靜態方式編譯來使編譯結果代碼得到最高的性能。使用靜態編譯和動態方式編譯的代碼相比,性能差距可能會達到5%到10%之多。
就我個人來說最常使用的編譯配置參數如下,各位可以參照自行增刪相關內容:
./configure --prefix=/usr/local/mysql \ --without-debug \ --without-bench \ --enable-thread-safe-client \ --enable-assembler \ --enable-profiling \ --with-mysqld-ldflags=-all-static \ --with-client-ldflags=-all-static \ --with-charset=latin1 \ --with-extra-charset=utf8,gbk \ --with-innodb \ --with-csv-storage-engine \ --with-federated-storage-engine \ --with-mysqld-user=mysql \ --without-embedded-server \ --with-server-suffix=-community \ --with-unix-socket-path=/usr/local/mysql/sock/mysql.sock
二、MySQL 日志設置優化
在安裝完MySQL之后,肯定是需要對MySQL的各種參數選項進行一些優化調整的。雖然MySQL系統的伸縮性很強,既可以在有很充足的硬件資源環境下高效的運行,也可以在極少資源環境下很好的運行,但不管怎樣,盡可能充足的硬件資源對MySQL的性能提升總是有幫助的。在這一節我們主要分析一下MySQL的日志(主要是Binlog)對系統性能的影響,並根據日志的相關特性得出相應的優化思路。
1.日志產生的性能影響
由於日志的記錄帶來的直接性能損耗就是數據庫系統中最為昂貴的IO資源,所以對於日志的在之前介紹 MySQL物理架構的章節中,我們已經了解到了 MySQL的日志包括錯誤日志( ErrorLog),更新日志( Update Log),二進制日志( Binlog),查詢日志( Query Log),慢查詢日志( Slow Query Log)等。當然,更新日志是老版本的MySQL才有的,目前已經被二進制日志替代。
在默認情況下,系統僅僅打開錯誤日志,關閉了其他所有日志,以達到盡可能減少IO損耗提高系統性能的目的。但是在一般稍微重要一點的實際應用場景中,都至少需要打開二進制日志,因為這是MySQL很多存儲引擎進行增量備份的基礎,也是MySQL實現復制的基本條件。有時候為了進一步的性能優化,定位執行較慢的SQL語句,很多系統也會打開慢查詢日志來記錄執行時間超過特定數值(由我們自行設置)的SQL語句。
一般情況下,在生產系統中很少有系統會打開查詢日志。因為查詢日志打開之后會將 MySQL中執行的每一條Query都記錄到日志中,會該系統帶來比較大的IO負擔,而帶來的實際效益卻並不是非常大。
一般只有在開發測試環境中,為了定位某些功能具體使用了哪些 SQL語句的時候,才會在短時間段內打開該日志來做相應的分析。所以,在MySQL系統中,會對性能產生影響的MySQL日志(不包括各存儲引擎自己的日志)主要就是Binlog了。
2.Binlog 相關參數及優化策略
我們首先看看Binlog 的相關參數,通過執行如下命令可以獲得關於Binlog 的相關參數。當然,其中也顯示出了“ innodb_locks_unsafe_for_binlog”這個Innodb 存儲引擎特有的與Binlog 相關的參數:
mysql> show variables like '%binlog%'; +--------------------------------+------------+ | Variable_name | Value | +--------------------------------+------------+ | binlog_cache_size | 1048576 | | innodb_locks_unsafe_for_binlog | OFF | | max_binlog_cache_size | 4294967295 | | max_binlog_size | 1073741824 | | sync_binlog | 0 | +--------------------------------+------------+
“binlog_cache_size”:在事務過程中容納二進制日志SQL 語句的緩存大小。二進制日志緩存是服務器支持事務存儲引擎並且服務器啟用了二進制日志(—log-bin 選項)的前提下為每個客戶端分配的內存,注意,是每個Client 都可以分配設置大小的binlog cache 空間。如果讀者朋友的系統中經常會出現多語句事務的話,可以嘗試增加該值的大小,以獲得更好的性能。當然,我們可以通過MySQL 的以下兩個狀態變量來判斷當前的binlog_cache_size 的狀況:Binlog_cache_use 和Binlog_cache_disk_use。
“max_binlog_cache_size”:和”binlog_cache_size”相對應,但是所代表的是binlog 能夠使用的最大cache 內存大小。當我們執行多語句事務的時候,max_binlog_cache_size 如果不夠大的話,系統可能會報出“ Multi-statement transaction required more than ‘max_binlog_cache_size’ bytes of
storage”的錯誤。
“max_binlog_size”:Binlog 日志最大值,一般來說設置為512M 或者1G,但不能超過1G。該大小並不能非常嚴格控制Binlog 大小,尤其是當到達Binlog 比較靠近尾部而又遇到一個較大事務的時候,系統為了保證事務的完整性,不可能做切換日志的動作,只能將該事務的所有SQL 都記錄進入當前日志,直到該事務結束。這一點和Oracle 的Redo 日志有點不一樣,因為Oracle 的Redo 日志所記錄的是數據文件的物理位置的變化,而且里面同時記錄了Redo 和Undo 相關的信息,所以同一個事務是否在一個日志中對Oracle 來說並不關鍵。而MySQL 在Binlog 中所記錄的是數據庫邏輯變化信息,MySQL 稱之為Event,實際上就是帶來數據庫變化的DML 之類的Query 語句。
“sync_binlog”:這個參數是對於MySQL 系統來說是至關重要的,他不僅影響到Binlog 對MySQL 所帶來的性能損耗,而且還影響到MySQL 中數據的完整性。對於“sync_binlog”參數的各種設置的說明如下:
● sync_binlog=0,當事務提交之后,MySQL 不做fsync 之類的磁盤同步指令刷新binlog_cache 中的信息到磁盤,而讓Filesystem 自行決定什么時候來做同步,或者cache 滿了之后才同步到磁盤。
● sync_binlog=n,當每進行n 次事務提交之后,MySQL 將進行一次fsync 之類的磁盤同步指令來將binlog_cache 中的數據強制寫入磁盤。
在MySQL 中系統默認的設置是sync_binlog=0,也就是不做任何強制性的磁盤刷新指令,這時候的性能是最好的,但是風險也是最大的。因為一旦系統Crash,在binlog_cache 中的所有binlog 信息都會被丟失。而當設置為“1”的時候,是最安全但是性能損耗最大的設置。因為當設置為1 的時候,即使系統Crash,也最多丟失binlog_cache 中未完成的一個事務,對實際數據沒有任何實質性影響。從以往經驗和相關測試來看,對於高並發事務的系統來說,“sync_binlog”設置為0 和設置為1 的系統寫入性能差距可能高達5 倍甚至更多。
大家都知道, MySQL的復制( Replication),實際上就是通過將Master端的Binlog通過利用IO線程通過網絡復制到Slave端,然后再通過SQL線程解析Binlog中的日志再應用到數據庫中來實現的。所
以, Binlog量的大小對IO線程以及Msater和Slave端之間的網絡都會產生直接的影響。
MySQL中Binlog的產生量是沒辦法改變的,只要我們的Query改變了數據庫中的數據,那么就必須將該Query所對應的Event記錄到Binlog中。那我們是不是就沒有辦法優化復制了呢?
當然不是,在MySQL復制環境中,實際上是是有8個參數可以讓我們控制需要復制或者需要忽略而不進行復制的DB或者Table的,分別為:
● Binlog_Do_DB:設定哪些數據庫( Schema)需要記錄Binlog;
● Binlog_Ignore_DB:設定哪些數據庫( Schema)不要記錄Binlog;
● Replicate_Do_DB:設定需要復制的數據庫( Schema),多個DB用逗號( “ ,”)分隔;
● Replicate_Ignore_DB:設定可以忽略的數據庫( Schema);
● Replicate_Do_Table:設定需要復制的Table;
● Replicate_Ignore_Table:設定可以忽略的Table;
● Replicate_Wild_Do_Table:功能同Replicate_Do_Table,但可以帶通配符來進行設置;
● Replicate_Wild_Ignore_Table:功能同Replicate_Ignore_Table,可帶通配符設置;
通過上面這八個參數,我們就可以非常方便按照實際需求,控制從Master端到Slave端的 Binlog量盡可能的少,從而減小Master端到Slave端的網絡流量,減少IO線程的IO量,還能減少SQL線程的
解析與應用SQL的數量,最終達到改善Slave上的數據延時問題。
實際上,上面這八個參數中的前面兩個是設置在Master端的,而后面六個參數則是設置在Slave端的。雖然前面兩個參數和后面六個參數在功能上並沒有非常直接的關系,但是對於優化MySQL的
Replication來說都可以起到相似的功能。當然也有一定的區別,其主要區別如下:
● 如果在Master端設置前面兩個參數,不僅僅會讓Master端的Binlog記錄所帶來的IO量減少,還會讓Master端的IO線程就可以減少Binlog的讀取量,傳遞給Slave端的IO線程的Binlog量自然就會較少。這樣做的好處是可以減少網絡IO,減少Slave端IO線程的IO量,減少Slave端的SQL線程的工作量,從而最大幅度的優化復制性能。當然,在Master端設置也存在一定的弊端,因為MySQL的判斷是否需要復制某個Event不是根據產生該Event的Query所更改的數據所在的DB,而是根據執行Query時刻所在的默認Schema,也就是我們登錄時候指定的DB或者運行“ USE DATABASE” 中所指定的DB。只有當前默認DB和配置中所設定的DB完全吻合的時候 IO線程才會將該Event讀取給Slave的IO線程。所以如果在系統中出現在默認DB和設定需要復制的DB不一樣的情況下改變了需要復制的DB中某個Table的數據的時候,該Event是不會被復制到Slave中去的,這樣就會造成Slave端的數據和Master的數據不一致的情況出現。同樣,如果在默認Schema下更改了不需要復制的Schema中的數據,則會被復制到Slave端,當Slave端並沒有該Schema的時候,則會造成復制出錯而停止;
● 而如果是在Slave端設置后面的六個參數,在性能優化方面可能比在Master端要稍微遜色一點,因為不管是需要還是不需要復制的Event都被會被IO線程讀取到Slave端,這樣不僅僅增加了網絡IO量,也給Slave端的IO線程增加了Relay Log的寫入量。但是仍然可以減少Slave的SQL線程在Slave端的日志應用量。雖然性能方面稍有遜色,但是在Slave端設置復制過濾機制,可以保證不會出現因為默認Schema的問題而造成Slave和Master數據不一致或者復制出錯的問題。
4.Slow Query Log相關參數及使用建議
再來看看Slow Query Log的相關參數配置。有些時候,我們為了定位系統中效率比較地下的 Query語句,則需要打開慢查詢日志,也就是Slow Query Log。我們可以如下查看系統慢查詢日志的相關設
置:
mysql> show variables like 'log_slow%'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | log_slow_queries | ON | +------------------+-------+ 1 row in set (0.00 sec) mysql> show variables like 'long_query%'; +-----------------+-------+ | Variable_name | Value | +-----------------+-------+ | long_query_time | 1 | +-----------------+-------+ 1 row in set (0.01 sec)
“ log_slow_queries” 參數顯示了系統是否已經打開 Slow Query Log 功能,而“ long_query_time” 參數則告訴我們當前系統設置的Slow Query 記錄執行時間超過多長的Query。在MySQL AB發行的MySQL版本中Slow Query Log可以設置的最短慢查詢時間為1秒,這在有些時候可能沒辦法完全滿足我們的要求,如果希望能夠進一步縮短慢查詢的時間限制,可以使用 Percona提供的microslow-patch(件成為msl Patch)來突破該限制。 msl patch不僅僅能將慢查詢時間減小到毫秒級別,同時還能通過一些特定的規則來過濾記錄的SQL,如僅記錄涉及到某個表的Slow Query等等附加功能。考慮到篇幅問題,這里就不介紹msl patch給我們帶來的更為詳細的功能和使用,大家請參考官方介紹( http://www.mysqlperformanceblog.com/2008/04/20/updated-msl-microslow-patchinstallation-walk-through/)
打開Slow Query Log功能對系統性能的整體影響沒有Binlog那么大,畢竟Slow Query Log的數據量比較小,帶來的IO損耗也就較小,但是,系統需要計算每一條Query的執行時間,所以消耗總是會有
一些的,主要是CPU方面的消耗。如果大家的系統在CPU資源足夠豐富的時候,可以不必在乎這一點點損耗,畢竟他可能會給我們帶來更大性能優化的收獲。但如果我們的 CPU資源也比較緊張的時候,也完全可以在大部分時候關閉該功能,而只需要間斷性的打開Slow Query Log功能來定位可能存在的慢查詢。
MySQL的其他日志由於使用很少( Query Log)或者性能影響很少,我們就不在此過多分析了,至於各個存儲引擎相關的日志,我們留在后面“ 常用存儲引擎優化” 部分再做相應的分析。
三、Query Cache 優化
談到Query Cache,恐怕使用過MySQL的大部分人都會或多或少有一些了解,因為在很多人看來他可以幫助我們將數據庫的性能產生一個“ 質” 的提升。但真的是這樣嗎?這一節我們就將如何合理的使用
MySQL 的Query Cache進行一些相應的分析並得出部分優化建議。
Query Cache真的是“ 尚方寶劍” 嗎?
MySQL 的 Query Cache實現原理實際上並不是特別的復雜,簡單的來說就是將客戶端請求的Query語句(當然僅限於SELECT類型的Query)通過一定的hash算法進行一個計算而得到一個hash值,存放
在一個hash桶中。同時將該Query的結果集( Result Set)也存放在一個內存Cache中的。存放Query hash值的鏈表中的每一個hash值所在的節點中同時還存放了該Query所對應的 Result Set 的Cache所
在的內存地址,以及該Query所涉及到的所有Table的標識等其他一些相關信息。系統接受到任何一個SELECT類型的Query的時候,首先計算出其hash值,然后通過該hash值到Query Cache中去匹配,如果找到了完全相同的Query,則直接將之前所Cache的 Result Set 返回給客戶端而完全不需要進行后面的任何步驟即可完成這次請求。而后端的任何一個表的任何一條數據發生變化之后,也會通知 Query Cache,需要將所有與該Table有關的Query的Cache全部失效,並釋放出之前占用的內存地址,以便后面其他的Query能夠使用。
從上面的實現原理來看, Query Cache確實是以比較簡單的實現帶來巨大性能收益的功能。但是很多人可能都忽略了使用QueryCache之后所帶來的負面影響:
a) Query語句的hash運算以及hash查找資源消耗。當我們使用Query Cache之后,每條SELECT類型的Query在到達MySQL之后,都需要進行一個 hash運算然后查找是否存在該 Query的Cache,雖然這個hash運算的算法可能已經非常高效了, hash查找的過程也已經足夠的優化了,對於一條Query來說消耗的資源確實是非常非常的少,但是當我們每秒都有上千甚至幾千條Query的時候,我們就不能對產生的CPU的消耗完全忽視了。
b) Query Cache的失效問題。如果我們的表變更比較頻繁,則會造成Query Cache的失效率非常高。這里的表變更不僅僅指表中數據的變更,還包括結構或者索引等的任何變更。也就是說我們每次緩存到Query Cache中的Cache數據可能在剛存入后很快就會因為表中的數據被改變而被清除,然后新的相同Query進來之后無法使用到之前的Cache。
c) Query Cache中緩存的是 Result Set ,而不是數據頁,也就是說,存在同一條記錄被Cache多次的可能性存在。從而造成內存資源的過渡消耗。當然,可能有人會說我們可以限定 Query Cache的大小啊。是的,我們確實可以限定Query Cache的大小,但是這樣, Query Cache就很容易造成因為內存不足而被換出,造成命中率的下降。
對於Query Cache的上面三個負面影響,如果單獨拿出每一個影響來說都不會造成對整個系統多大的問題,並不會讓大家對使用Query Cache產生太多顧慮。但是,當綜合這三個負面影響一起考慮的話,恐怕Query Cache在很多人心目中就不再是以前的那把“ 尚方寶劍” 了。
適度使用Query Cache
雖然Query Cache 的使用會存在一些負面影響,但是我們也應該相信其存在是必定有一定價值。我們完全不用因為Query Cache的上面三個負面影響就完全失去對Query Cache的信心。只要我們理解了
Query Cache的實現原理,那么我們就完全可以通過一定的手段在使用Query Cache的時候揚長避短, 重發發揮其優勢,並有效的避開其劣勢。
首先,我們需要根據Query Cache失效機制來判斷哪些表適合使用Query哪些表不適合。由於Query Cache的失效主要是因為Query所依賴的Table的數據發生了變化,造成Query的 Result Set 可能已經
有所改變而造成相關的Query Cache全部失效,那么我們就應該避免在查詢變化頻繁的Table的Query上使用,而應該在那些查詢變化頻率較小的Table的Query上面使用。 MySQL中針對Query Cache有兩個專用的SQL Hint(提示): SQL_NO_CACHE和SQL_CACHE,分別代表強制不使用Query Cache和強制使用Query Cache。我們完全可以利用這兩個SQL Hint,讓MySQL知道我們希望哪些SQL使用Query Cache而哪些SQL就不要使用了。這樣不僅可以讓變化頻繁Table的Query浪費Query Cache的內存,同時還可以減少Query Cache的檢測量。
其次,對於那些變化非常小,大部分時候都是靜態的數據,我們可以添加 SQL_CACHE的SQL Hint,強制MySQL使用Query Cache,從而提高該表的查詢性能。
最后,有些SQL的 Result Set 很大,如果使用Query Cache很容易造成Cache內存的不足,或者將之前一些老的Cache 沖刷出去。對於這一類Query我們有兩種方法可以解決,一是使用SQL_NO_CACHE參數來強制他不使用 Query Cache 而每次都直接從實際數據中去查找,另一種方法是通過設定“ query_cache_limit” 參數值來控制 Query Cache 中所 Cache 的最大 Result Set ,系統默認為1M( 1048576)。當某個Query的 Result Set 大於“ query_cache_limit” 所設定的值的時候, Query Cache是不會Cache這個Query的。
Query Cache的相關系統參數變量和狀態變量
我們首先看看Query Cache的系統變量,可以通過執行如下命令獲得MySQL中Query Cache相關的系統參數變量:
mysql> show variables like '%query_cache%'; +------------------------------+-----------+ | Variable_name | Value | +------------------------------+-----------+ | have_query_cache | YES | | query_cache_limit | 1048576 | | query_cache_min_res_unit | 4096 | | query_cache_size | 268435456 | | query_cache_type | ON | | query_cache_wlock_invalidate | OFF | +------------------------------+-----------+
● “ have_query_cache” :該MySQL是否支持Query Cache;
● “ query_cache_limit” : Query Cache存放的單條Query最大 Result Set ,默認1M;
● “ query_cache_min_res_unit” : Query Cache 每個 Result Set 存放的最小內存大小,默認
4k;
● “ query_cache_size” :系統中用於Query Cache內存的大小;
● “ query_cache_type” :系統是否打開了Query Cache功能;
● “ query_cache_wlock_invalidate” :針對於 MyISAM存儲引擎,設置當有 WRITE LOCK 在某個Table 上面的時候,讀請求是要等待 WRITE LOCK 釋放資源之后再查詢還是允許直接從 Query
Cache中讀取結果,默認為FALSE(可以直接從Query Cache中取得結果)。
以上參數的設置主要是“ query_cache_limit” 和“ query_cache_min_res_unit” 兩個參數的設置需要做一些針對於應用的相關調整。如果我們需要 Cache的 Result Set 一般都很小(小於 4k)的話,可以適當將 “query_cache_min_res_unit ” 參數再調小一些,避免造成內存的浪費,“ query_cache_limit” 參數則不用調整。而如果我們需要Cache的 Result Set 大部分都大於4k的話,則最好將“query_cache_min_res_unit” 調整到和 Result Set 大小差不多, “ query_cache_limit” 的參數也應大於 Result Set 的大小。當然,可能有些時候我們比較難准確的估算 Result Set 的大小,那么當 Result Set 較大的時候,我們也並不是非得將“ query_cache_min_res_unit” 設置的和每個Result Set 差不多大,是每個結果集的一半或者四分之一大小都可以,要想非常完美的完全不浪費任何內存確實也是不可能做到的。
如果我們要了解Query Cache的使用情況,則可以通過Query Cache相關的狀態變量來獲取,如通過如下命令:
mysql> show status like 'Qcache%'; +-------------------------+------------+ | Variable_name | Value | +-------------------------+------------+ | Qcache_free_blocks | 7499 | | Qcache_free_memory | 190662000 | | Qcache_hits | 1888430018 | | Qcache_inserts | 1014096388 | | Qcache_lowmem_prunes | 106071885 | | Qcache_not_cached | 7951123988 | | Qcache_queries_in_cache | 19315 | | Qcache_total_blocks | 47870 | +-------------------------+------------+
● “ Qcache_free_blocks” : Query Cache 中目前還有多少剩余的 blocks。如果該值顯示較大,則說明Query Cache中的內存碎片較多了,可能需要尋找合適的機會進行整理。
● “ Qcache_free_memory” : Query Cache中目前剩余的內存大小。通過這個參數我們可以較為准確的觀察出當前系統中的Query Cache內存大小是否足夠,是需要增加還是過多了;
● “ Qcache_hits” :多少次命中。通過這個參數我們可以查看到Query Cache的基本效果;
● “ Qcache_inserts” :多少次未命中然后插入。通過“ Qcache_hits” 和“ Qcache_inserts” 兩個參數我們就可以算出Query Cache的命中率了:Query Cache 命中率 = Qcache_hits / ( Qcache_hits + Qcache_inserts );
● “ Qcache_lowmem_prunes” :多少條 Query 因為內存不足而被清除出 Query Cache。通過“ Qcache_lowmem_prunes” 和“ Qcache_free_memory” 相互結合,能夠更清楚的了解到我們系統中Query Cache的內存大小是否真的足夠,是否非常頻繁的出現因為內存不足而有Query被換出
● “ Qcache_not_cached” :因為query_cache_type的設置或者不能被cache的Query的數量;
● “ Qcache_queries_in_cache” :當前Query Cache中cache的Query數量;
● “ Qcache_total_blocks” :當前Query Cache中的block數量;
Query Cache的限制
Query Cache由於存放的都是邏輯結構的 Result Set,而不是物理的數據頁,所以在性能提升的同時,也會受到一些特定的限制。
a) 5.1.17之前的版本不能Cache 綁定變量的Query,但是從5.1.17版本開始, Query Cache已經開始支持幫定變量的Query了;
b) 所有子查詢中的外部查詢SQL不能被Cache;
c) 在Procedure, Function以及Trigger中的Query不能被Cache;
d) 包含其他很多每次執行可能得到不一樣結果的函數的Query不能被Cache。
鑒於上面的這些限制,在使用Query Cache 的過程中,建議通過精確設置的方式來使用,僅僅讓合適的表的數據可以進入 Query Cache,僅僅讓某些 Query 的查詢結果被 Cache。
四、MySQL Server其他常用優化
除了安裝,日志, Query Cache之外,可能影響 MySQL Server整體性能的設置其他很多方面,如網絡連接,線程管理, Table 管理等。這一節我們將分析除了前面幾節內容之外的可能影響 MySQL Server性能的其他可優化的部分。
1)網絡連接與連接線程
雖然 MySQL的連接方式不僅僅只有通過網絡方式,還可以通過命名管道的方式,但是不論是何種方式連接 MySQL,在 MySQL 中都是通過線程的方式管理所有客戶端請求的連接。每一個客戶端連接都會有一個與之對應的生成一個連接線程。我們先看一下與網絡連接的性能配置項及對性能的影響。
● max_conecctions:整個MySQL允許的最大連接數;
這個參數主要影響的是整個 MySQL 應用的並發處理能力,當系統中實際需要的連接量大於max_conecctions的情況下,由於MySQL的設置限制,那么應用中必然會產生連接請求的等待,從而限制了相應的並發量。所以一般來說,只要 MySQL主機性能允許,都是將該參數設置的盡可能大一點。一般來說500到800左右是一個比較合適的參考值
● max_user_connections:每個用戶允許的最大連接數;
上面的參數是限制了整個MySQL的連接數,而max_user_connections則是針對於單個用戶的連接限制。在一般情況下我們可能都較少使用這個限制,只有在一些專門提供 MySQL數據存儲服務,或者是提供虛擬主機服務的應用中可能需要用到。除了限制的對象區別之外,其他方面和max_connections一樣。這個參數的設置完全依賴於應用程序的連接用戶數,對於普通的應用來說,完全沒有做太多的限制,可以盡量放開一些。
● net_buffer_length:網絡包傳輸中,傳輸消息之前的net buffer初始化大小;
這個參數主要可能影響的是網絡傳輸的效率,由於該參數所設置的只是消息緩沖區的初始化大小,所以造成的影響主要是當我們的每次消息都很大的時候MySQL總是需要多次申請擴展該緩沖區大小。系統默認大小為16KB,一般來說可以滿足大多數場景,當然如果我們的查詢都是非常小,每次網絡傳輸量都很少,而且系統內存又比較緊缺的情況下,也可以適當將該值降低到8KB。
● max_allowed_packet:在網絡傳輸中,一次傳消息輸量的最大值;
這個參數與net_buffer_length相對應,只不過是net buffer的最大值。當我們的消息傳輸量大於net_buffer_length的設置時, MySQL會自動增大net buffer的大小,直到緩沖區大小達到max_allowed_packet所設置的值。系統默認值為1MB,最大值是1GB,必須設定為1024的倍數,單位為字節。
● back_log:在MySQL的連接請求等待隊列中允許存放的最大連接請求數。
連接請求等待隊列,實際上是指當某一時刻客戶端的連接請求數量過大的時候, MySQL主線程沒辦法及時給每一個新的連接請求分配(或者創建)連接線程的時候,還沒有分配到連接線程的所有請求將存放在一個等待隊列中,這個隊列就是MySQL的連接請求隊列。當我們的系統存在瞬時的大量連接請求的時候,則應該注意back_log參數的設置。系統默認值為50,最大可以設置為65535。當我們增大back_log的設置的時候,同時還需要注意OS級別對網絡監聽隊列的限制,因為如果OS的網絡監聽設置小於MySQL的back_log設置的時候,我們加大“ back_log” 設置是沒有意義的。
上面介紹了網絡連接交互相關的主要優化設置,下面我們再來看看與每一個客戶端連接相對應的連接線程。
在MySQL中,為了盡可提高客戶端請求創建連接這個過程的性能,實現了一個Thread Cache池,將空閑的連接線程存放在其中,而不是完成請求后就銷毀。這樣,當有新的連接請求的時候, MySQL首先會檢查Thread Cache池中是否存在空閑連接線程,如果存在則取出來直接使用,如果沒有空閑連接線程,才創建新的連接線程。在MySQL中與連接線程相關的系統參數及狀態變量說明如下:
● thread_cache_size: Thread Cache池中應該存放的連接線程數。
當系統最初啟動的時候,並不會馬上就創建thread_cache_size所設置數目的連接線程存放在Thread Cache池中,而是隨着連接線程的創建及使用,慢慢的將用完的連接線程存入其中。當存放的連接線程達到thread_cache_size值之后, MySQL就不會再續保存用完的連接線程了。
如果我們的應用程序使用的短連接, Thread Cache池的功效是最明顯的。因為在短連接的數據庫應用中,數據庫連接的創建和銷毀是非常頻繁的,如果每次都需要讓 MySQL新建和銷毀相應的連接線程,那么這個資源消耗實際上是非常大的,而當我們使用了Thread Cache之后,由於連接線程大部分都是在創建好了等待取用的狀態,既不需要每次都重新創建,又不需要在使用完之后銷毀,所以可以節省下大量的系統資源。所以在短連接的應用系統中,thread_cache_size的值應該設置的相對大一些,不應該小於應用系統對數據庫的實際並發請求數。
而如果我們使用的是長連接的時候, Thread Cache的功效可能並沒有使用短連接那樣的大,但也並不是完全沒有價值。因為應用程序即使是使用了長連接,也很難保證他們所管理的所有連接都能處於很穩定的狀態,仍然會有不少連接關閉和新建的操作出現。在有些並發量較高,應用服務器數量較大的系統中,每分鍾十來次的連接創建與關閉的操作是很常見的。而且如果應用服務器的連接池管理不是太好,容易產生連接池抖動的話,所產生的連接創建和銷毀操作將會更多。所以即使是在使用長連接的應用環境中, Thread Cache機制的利用仍然是對性能大有幫助的。只不過在長連接的環境中我們不需要將thread_cache_size參數設置太大,一般來說可能50到100之間應該就可以了。
● thread_stack:每個連接線程被創建的時候, MySQL給他分配的內存大小。
當MySQL創建一個新的連接線程的時候,是需要給他分配一定大小的內存堆棧空間,以便存放客戶端的請求Query以及自身的各種狀態和處理信息。不過一般來說如果不是對MySQL的連接線程處理機制十分熟悉的話,不應該輕易調整該參數的大小,使用系統的默認值( 192KB)基本上可以所有的普通應用環境。如果該值設置太小,會影響 MySQL連接線程能夠處理客戶端請求的Query內容的大小,以及用戶創建的Procedures和Functions等。
上面介紹的這些都是我們可以怎樣配置網絡連接交互以及連接線程的性能相關參數,下面我們再看看該怎樣檢驗上面所做的設置是否合理,是否有需要調整的地方。我們可以通過在系統中執行如下的幾個命令來獲得相關的狀態信息來幫助大家檢驗設置的合理性:
我們現看看連接線程相關的系統變量的設置值:
mysql> show variables like 'thread%'; +-------------------+--------+ | Variable_name | Value | +-------------------+--------+ | thread_cache_size | 64 | | thread_stack | 196608 | +-------------------+--------+
再來看一下系統被連接的次數以及當前系統中連接線程的狀態值:
mysql> show status like 'connections'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | Connections | 127 | +---------------+-------+ mysql> show status like '%thread%'; +------------------------+-------+ | Variable_name | Value | +------------------------+-------+ | Delayed_insert_threads | 0 | | Slow_launch_threads | 0 | | Threads_cached | 4 | | Threads_connected | 7 | | Threads_created | 11 | | Threads_running | 1 | +------------------------+-------+
通過上面的命令,我們可以看出,系統設置了Thread Cache池最多將緩存32個連接線程,每個連接線程創建之初,系統分配192KB的內存堆棧空給他。系統啟動到現在共接收到客戶端的連接127次,共創建了11個連接線程,但前有7個連接線程處於和客戶端連接的狀態,而7個連接狀態的線程中只有一個是active狀態,也就是說只有一個正在處理客戶端提交的請求。而在Thread Cache池中當共Cache了4個連接線程。
通過系統設置和當前狀態的分析,我們可以發現, thread_cache_size的設置已經足夠了,甚至還遠大於系統的需要。所以我們可以適當減少 thread_cache_size 的設置,比如設置為 8 或者 16。根據Connections 和 Threads_created 這兩個系統狀態值,我們還可以計算出系統新建連接連接的 Thread Cache命中率,也就是通過 Thread Cache池中取得連接線程的次數與系統接收的總連接次數的比率,如下:
Threads_Cache_Hit = (Connections - Threads_created) / Connections * 100%
我們可以通過上面的這個運算公式計算一下上面環境中的Thread Cache命中率: Thread_Cache_Hit = (127 - 12) / 127 * 100% = 90.55%
一般來說,當系統穩定運行一段時間之后,我們的 Thread Cache命中率應該保持在 90%左右甚至更高的比率才算正常。可以看出上面環境中的Thread Cache命中比率基本還算是正常的。
2)Table Cache相關的優化
我們先來看一下 MySQL 打開表的相關機制。由於多線程的實現機制,為了盡可能的提高性能,在MySQL中每個線程都是獨立的打開自己需要的表的文件描述符,而不是通過共享已經打開的表的文件描述符的機制來實現。當然,針對於不同的存儲引擎可能有不同的處理方式。如MyISAM表,每一個客戶端線程打開任何一個MyISAM表的數據文件都需要打開一個文件描述符,但如果是索引文件,則可以多個線程共享同一個索引文件的描述符。對於Innodb的存儲引擎,如果我們使用的是共享表空間來存儲數據,那么我們需要打開的文件描述符就比較少,而如果我們使用的是獨享表空間方式來存儲數據,則同樣,由於存儲表數據的數據文件較多,則同樣會打開很多的表文件描述符。除了數據庫的實際表或者索引打開以外,臨時文件同樣也需要使用文件描述符,同樣會占用系統中open_files_limit的設置限額。
為了解決打開表文件描述符太過頻繁的問題, MySQL在系統中實現了一個Table Cache的機制,和前面介紹的Thread Cache機制有點類似,主要就是Cache打開的所有表文件的描述符,當有新的請求的時候不需要再重新打開,使用結束的時候也不用立即關閉。通過這樣的方式來減少因為頻繁打開關閉文件描述符所帶來的資源消耗。我們先看一看Table Cache相關的系統參數及狀態變量。
在MySQL中我們通過table_cache(從MySQL5.1.3開始改為table_open_cache),來設置系統中為我們Cache的打開表文件描述符的數量。通過MySQL官方手冊中的介紹,我們設置table_cache大小的時候應該通過max_connections參數計算得來,公式如下:
table_cache = max_connections * N;
其中N代表單個Query語句中所包含的最多Table的數量。但是我個人理解這樣的計算其實並不是太准確,分析如下:
首先, max_connections是系統同時可以接受的最大連接數,但是這些連接並不一定都是 active狀態的,也就是說可能里面有不少連接都是處於 Sleep狀態。而處於 Sleep狀態的連接是不可能打開任何Table的。
其次,這個N為執行Query中包含最多的Table的Query所包含的Table的個數也並不是太合適, 因為我們不能忽略索引文件的打開。雖然索引文件在各個連接線程之間是可以共享打開的連接描述符的,但總還是需要的。而且,如果我 Query 中的每個表的訪問都是通過現通過索引定位檢索的,甚至可能還
是通過多個索引,那么該Query的執行所需要打開的文件描述符就更多了,可能是N的兩倍甚至三倍。
最后,這個計算的公式只能計算出我們同一時刻需要打開的描述符的最大數量,而 table_cache的設置也不一定非得根據這個極限值來設定,因為table_cache所設定的只是Cache打開的描述符的數量的大小,而不是最多能夠打開的量的大小。
當然,上面的這些只是我個人的理解,也可能並不是太嚴謹,各位讀者朋友如果覺得有其他的理解完全可以提出來大家再探討。
我們可以通過如下方式查看table_cache的設置和當前系統中的使用狀況:
mysql> show variables like 'table_cache'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | table_cache | 512 | +---------------+-------+ mysql> show status like 'open_tables'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | Open_tables | 6 | +---------------+-------+
上面的結果顯示系統設置的 table_cache為512 個,也就是說在該 MySQL 中, Table Cache中可以Cache 512 個打開文件的描述符;當前系統中打開的描述符僅僅則只有6個。
那么Table Cache池中Cache的描述符在什么情況下會被關閉呢?一般來說主要有以下集中情況會出現被Cache的描述符被關閉:
a) Table Cache的Cache池滿了,而某個連接線程需要打開某個不在Table Cache中的表時, MySQL會通過一定的算法關閉某些沒有在使用中的描述符;
b) 當我們執行Flush Table等命令的時候, MySQL會關閉當前Table Cache中Cache的所有文件描述符;
c) 當Table Cache中Cache的量超過table_cache參數設置的值的時候;
Sort Buffer, Join Buffer和Read Buffer在MySQL中,之前介紹的多種Cache之外,還有在Query執行過程中的兩種Buffer會對數據庫的整體性能產生影響。
mysql> show variables like '%buffer%'; +-------------------------------+----------+ | Variable_name | Value | +-------------------------------+----------+ ... ... | join_buffer_size | 4190208 | ... ... | sort_buffer_size | 2097144 | +-------------------------------+----------+
● join_buffer_size:當我們的 Join 是 ALL, index, rang 或者 index_merge 的時候使用的Buffer;
實際上這種Join被稱為Full Join。實際上參與 Join的每一個表都需要一個Join Buffer,所以在Join出現的時候,至少是兩個。 Join Buffer的設置在MySQL 5.1.23版本之前最大為4GB,但是從5.1.23版本開始,在除了Windows之外的64位的平台上可以超出4BG的限制。系統默認是128KB。
● sort_buffer_size:系統中對數據進行排序的時候使用的Buffer;
Sort Buffer同樣是針對單個Thread的,所以當多個Thread同時進行排序的時候,系統中就會出現多個 Sort Buffer。一般我們可以通過增大 Sort Buffer的大小來提高 ORDER BY 或者是 GROUP BY的處理性能。系統默認大小為 2MB,最大限制和 Join Buffer一樣,在 MySQL 5.1.23版本之前最大
為4GB,從5.1.23版本開始,在除了Windows之外的64 位的平台上可以超出4GB的限制。
如果應用系統中很少有Join語句出現,則可以不用太在乎join_buffer_size參數的大小設置,但是如果Join語句不是很少的話,個人建議可以適當增大join_buffer_size的設置到1MB左右,如果內存充足甚至可以設置為 2MB。對於 sort_buffer_size參數來說,一般設置為 2MB到 4MB之間可以滿足大多數
應用的需求。當然,如果應用系統中的排序都比較大,內存充足且並發量不是特別的大的時候,也可以繼續增大 sort_buffer_size 的設置。在這兩個 Buffer設置的時候,最需要注意的就是不要忘記是每個Thread都會創建自己獨立的Buffer,而不是整個系統共享的Buffer,不要因為設置過大而造成系統內存
不足。
五、小結
通過參數設置來進行性能優化所能夠帶來的性能提升可能並不會如很多人想象的那樣產生質的飛躍,除非是之前的設置存在嚴重的不合理情況。我們不能將性能調優完全依托在通過 DBA 在數據庫上線后的參數調整之上,而應該在系統設計和開發階段就盡可能減少性能問題。當然,也不能否認參數調整
在某些場景下對系統性能的影響比較大,但畢竟只是少數的特殊情況。