MySQL:教你學會如何做性能分析與查詢優化

優化應貫穿整個產品開發週期中,比如編寫複雜SQL時查看執行計劃,安裝MySQL服務器時儘量合理配置(見過太多完全使用默認配置安裝的情況),根據應用負載選擇合理的硬件配置等。

1、性能分析

性能分析包含多方面:CPU、Memory、磁盤/網絡IO、MySQL服務器本身等。

1.1 操作系統分析

常規的操作系統分析,在Linux中通常包含一些性能監控命令,如top、vmstat、iostat、strace、iptraf等。

  • 內存:內存是大項,高查詢消耗大量的查詢緩存,內存必須足夠,並且給系統本身要預留一些。

  • 磁盤:配備高速磁盤+RAID會有更好的讀寫速度,並且SSD成本逐漸降低,升級成本會在可接受範圍。

  • 網絡:目前市場上千兆萬兆網卡已很常見。

  • CPU:雖然很多情況下CPU用不完,但也不能讓它成爲瓶頸。

生產環境的MySQL多數情況部署在Linux系統中,Linux系統本身可以優化的配置並不多。硬件的選型是複雜,涉及計算機組成的原理性知識,需要額外瞭解。

1.2 MySQL服務性能分析

MySQL服務器的性能通常通過監控命令查看系統工作狀態,確定哪些因素成爲瓶頸。

1.2.1 SHOW GLOBAL STATUS

顯示了目前MySQL的工作狀態,包含很多參數,下面對一些參數進行說明,其餘的參考官方說明:


1.Aborted_clients

如果該值隨時間增加,檢查是否優雅關閉連接,檢查max_allowed_packet配置變量是否被超過導致強制中斷。

2.Aborted_connections

接近於0,檢查網絡問題,如果有少量是正常的,比如鑑權失敗等。

3.Binlog_cache_disk_use和Binlog_cache_use

大部分事務應該在緩衝中進行,如果disk cache很大,可考慮增加內存緩存。

4.Bytes_recevied和Bytes_sent

如果值很大,檢查是否查詢超過需要的數據。

5.Com_*

儘量讓如Com_rollback這些不常見的變量超過預期,用innotop檢查。

6.Create_tmp_tables

優化查詢降低該值。

7.Handler_read_rnd_next

Handler_read_rnd_next / Handler_read_rnd顯示全表掃面大致平均值,如果很大,只能優化查詢。

8.Open_files

不應該接近於open_files_limit,如果接近就應該適當增加open_files_limit。

9.Qcache_*

查詢緩存相關。

10.Select_full_join

全聯接無索引聯接,儘量避免,優化查詢。

11.Select_full_range_join

值過高說明使用了範圍查詢聯接表,範圍查詢比較慢,可優化。

12.Sort_meger_passes

如果值較大可考慮增加sort_buffer_size,查明是那個查詢導致使用文件排序。

13.Table_locks_waited

表被鎖定導致服務器鎖等待,InnoDB的行鎖不會使得該變量增加,建議開啓慢查詢日誌。

14.Threads_created

如果值在增加,可考慮增加thread_cache_size。


1.2.2 SHOW ENGINE INNODB STATUS

暫時的數據包含了太多InnoDB核心信息,並且需要比較深的瞭解InnoDB引擎工作原理,這裏不做過多說明,請查閱針對此的專項文檔。

注: 通常包含SEMAPHORES、TRANSACTIONS、FILE I/O、LOG、BUFFER POOL AND MEMORY等一些詳細值,有些參數是上一次執行以來的平均值,所以建議隔一段時間再打印一次得到這段時間的統計,有點類似iostat的統計磁盤平均讀寫一樣。

1.2.3 開啓慢查詢日誌配置

排查導致MySQL運行緩慢的問題SQL,開啓慢查詢日誌配置,可能有很有幫助:


 

slow_query_log=1
slow_query_log_file=/YOUR_DIR/mysql_slow.log

配合慢查詢日誌分析工具(如mysqlsla)

2、查詢性能優化

一般來說在編寫SQL時,注意查詢是否能使用到索引,是否在大表中或者高頻率查詢中引起全表掃描,這些主要通過經驗分析配合execution plan得到比較理想的查詢消耗。

MySQL查詢優化,也可以參考:MySQL:數據庫優化,看這篇就夠了

2.1 查詢基礎

瞭解查詢過程,才能知道哪些步驟可能出現瓶頸,execution plan結果也會有所體現,MySQL查詢的一般過程:

  1. Client往服務器發送查詢指令。

  2. 服務器查詢緩存,如果存在則直接返回,否則下一步。

  3. 服務器解析、預處理和優化查詢,生成執行計劃。

  4. 執行引擎調用存儲引擎API執行查詢。

  5. 服務器將結果返回至客戶端。

用圖表示如下:

解析與預處理過程:

  • 解析器將查詢分解後構造解析樹,進行語法解析與驗證查詢,檢查SQL是否有效。

  • 預處理器解析語義:如檢查表和列是否存在,是否存在歧義等。

  • 預處理器檢查權限。

查詢優化器:

該過程比較複雜,將解析樹的結果變成執行計劃,優化器的任務是尋找最好的方式(但並不是總能選擇最好的方案),MySQL使用基於開銷的優化器,預測不同執行計劃的開銷。

  • MySQL不考慮不受它控制的開銷,如用戶存儲過程與用戶自定義的函數

  • 不考慮正在運行的其他查詢

2.2 優化數據訪問 (這一點很重要)

  1. 應用程序是否獲取超過需要的數據量?(PS: 多次遇到過查詢表所有數據然後再程序中只讀取10行之類的代碼)

  2. MySQL 服務器是否分析了超過需要的行?數據是否沒有在存儲引擎層被過來掉?(Using index , Using where)

典型的錯誤如下:

  1. 提取超過需要的行,然後在程序中只要一部分 (應該使用limit限制數據量)。

  2. 多表join提取所有的列 (應該只讀取需要的列)。

  3. 提取所有的列(提取不需要的列可能導致優化索引失效,增加磁盤IO,浪費內存等, 但如果是知道這個影響並利用查詢緩存,簡化設計等也是可以考慮的)。

訪問類型:

Full Table Scan > Index Scan > Range Scan > Unique Index Lookup > Constant.

訪問速度依次遞增。

對於使用where語句來過濾數據的話,最好到最壞的情況是:

  1. 對索引查找用where來消除不匹配的數據行,在存儲引擎層。

  2. 使用覆蓋索引 (Extra 爲Using Index) 來避免訪問行,取得索引數據後過濾行,發生在MySQL服務器層,但不需要讀取行數據。

  3. 從表中查詢數據,然後過濾 (Using Where), 發生在服務器端並且要讀取行數據。

後面會針對執行計劃結果做詳細介紹。

2.3 關於執行計劃

執行計劃結果樣例如下圖(也可用其他的可視化工具,如mysql workbench):

所代表的含義可在官方文檔中找到詳細說明 ( https://dev.mysql.com/doc/refman/5.5/en/explain-output.html ),

這裏說明一些比較重要的結果:

TYPE字段的值:

前面所說的訪問速度依次遞增就和這個有關:

Full Table Scan > Index Scan > Range Scan > Unique Index Lookup > Constant.

這裏列出一些常見的說明:

1、const: 最多匹配一行, 如 SELECT * FROM rental where rental_id=1。

2、eq_ref: 讀取的行依次匹配前一個表。

3、ref: 連接僅使用左索引或者索引不是PRIMARY或UNIQUE(或者說得到的不是一行的結果),如果得到的幾行數據,這是個比較好的類型。

4、range: 使用索引的範圍掃描,如使用了 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN()等條件。

5、index: 除了索引樹被掃描之外,索引連接類型與ALL相同。這有兩種方式:


  1. 如果索引是查詢的覆蓋索引,並滿足表中所需的所有數據,則僅掃描索引樹。在這種情況下,Extra列爲Using index。僅索引掃描通常比ALL更快,因爲索引的大小通常小於表數據。

  2. 使用索引來執行全表掃描,以按索引順序查找數據行。在Extra列張則沒有Using index,這種情況與ALL的區別是ALL是按行掃描。


6、ALL: 全表掃描,比較糟糕 (但有時候數據比較少的情況下,MySQL會直接進行全表掃描讀取數據,效率更高)。

2.4 優化特定的查詢

查詢優化的一個辦法是遷移舊數據,騰出內存空間重新平衡索引結構,使得更快的查詢速度,很多應用保留半年或三個月的數據都能滿足需求,對於舊數據,額外提供平臺訪問或者在應用層做路由。

2.4.1 優化COUNT (遇到過一知半解的使用,導致想優化卻適得其反)

COUNT有兩種不同的工作方式:統計值的數量和統計行的數量。

值是一個非空(Non-NULL)的表達式(NULL則表示沒有值),如果在COUNT()中定義了列名或其他表達式,COUNT則會統計這個表達式有值(Non-NULL)的次數。

COUNT另外一種工作方式就是統計行數,當MySQL知道括號中的表達式不會爲NULL的時候,則使用這種方式,COUNT(*)是個例子,它不會展開成所有列,則是忽略所以的列並統計。

2.4.2 優化limit和offset

偏移量很大的查詢代價很高,如LIMIT 10000, 10, 則會產生10010數據,然後只截取10行。

解決辦法:

  1. 限制分頁能讀取的數據頁數。

  2. 可考慮使用覆蓋索引,如 select id, name, description from book limit 100,10;

在ID上有索引改進爲:select id, name, description from book inner join (select id from book limit 100, 10) as b;

發佈了91 篇原創文章 · 獲贊 97 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章