Mysql慢查詢優化

開啓 MySQL 慢查詢日誌

一個起步就不簡單的原因是,我們如何才能定位到那些真正形成瓶頸的慢查詢。一個普通項目中的 SQL 可能就有大幾十甚至上百個,而「兇手們」就藏匿其中。

一個樸素的想法是在項目中每一個 SQL 執行前後打上時間戳來估計執行時間,暫且不論由於各種因素的影響這種估算可能不準確,更讓人不可接受的是這對原始代碼造成的極大的侵入。

好在 MySQL 提供了慢查詢日誌。這個日誌會記錄所有執行時間超過 long_query_time(默認是 10s)的 SQL 及相關的信息。

在 MySQL 的 Console 中:

mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set

mysql> show variables like 'slow_query%';
+---------------------+--------------------------------------+
| Variable_name       | Value                                |
+---------------------+--------------------------------------+
| slow_query_log      | OFF                                  |
| slow_query_log_file | /var/log/mysql/log-slow-queries.log  |
+---------------------+--------------------------------------+
2 rows in set

在這裏,slow_query_log 指的是慢查詢日誌是否開啓,slow_query_log_file 指明瞭日誌所在的位置。

在 MySQL 的配置文件 my.cnf 的 [mysqld] 項下可以配置慢查詢日誌開啓,一般來講如下配置即可:

 

[mysqld]
slow_query_log=1
slow_query_log_file=/var/log/mysql/log-slow-queries.log
long_query_time=2

需要注意的是,應該賦予 slow_query_log_file 指向的目錄 mysql 用戶的寫入權限:chown mysql.mysql -R /var/log/mysql,一般用默認的目錄就好。詳細的文檔可以看這裏:6.4.5 The Slow Query Log

之後需重載 MySQL 配置生效:/etc/init.d/mysql reload。

分析慢查詢日誌

在開啓了 MySQL 慢查詢日誌一段時間之後,日誌中就會把所有超過 long_query_time 的 SQL 記錄下來。另一個有用的相關 MySQL 命令是 mysqldumpslow:由於慢查詢日誌可能很大或者很難分析,使用它可以獲得 MySQL 對慢查詢日誌的一個總結報告,直接獲得我們想要的統計分析後的結果。詳細的文檔可以看這裏:5.6.8 mysqldumpslow — Summarize Slow Query Log Files

當然,你也可以打開日誌文件自己來查詢、分析。

優化 SQL

在得知哪些 SQL 是慢查詢之後,我們就可以定位到具體的業務接口並針對性的進行優化了。

首先,你要看是否能在不改變現有業務邏輯的前提下改進查詢的速度。一個典型的場景是,你需要查詢數據庫中是否存在符合某個條件的記錄,返回一個布爾值來表示有或者沒有,一般用於通知提醒。如果程序員在撰寫接口時沒把性能放在心上,那麼他就有可能寫出 SELECT count(*) FROM tbl_xxx WHERE XXXX 這樣的查詢,當數據量一大時(而且索引不恰當或沒有索引)這個查詢會相當之慢,但如果改成 SELECT id FROM tbl_xxx WHERE XXXX LIMIT 1 這樣來查詢,對速度的提升則是巨大的。這個例子並不是我憑空捏造的,最近在實際項目中我就看到了跟這個例子一模一樣的場景。

能夠找到上述的通過改變查詢方式而又不改變業務邏輯的慢查詢是幸運的,因爲這些場景往往意味着只需重寫 SQL 語句就能帶來顯著的性能提升,而且稍有經驗的程序員在一開始就不會寫出能夠明顯改良的查詢語句。在絕大多數情況下,SQL 足夠複雜而且難以做任何有價值的改動,這時就需要通過優化索引來提升效率了。

如何更好的創建數據庫索引絕對是一門技術活,我也並不覺得簡簡單單就能釐得很清楚,很多時候還是得具體 SQL 具體分析,甚至多條 SQL 一起來分析。可以先讀一讀這篇美團點評技術團隊的文章:MySQL索引原理及慢查詢優化,更深入的瞭解則可以閱讀《高性能MySQL》一書。引用一下美團點評技術團隊文章中提到的幾個原則:

  1. 最左前綴匹配原則,非常重要的原則,mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整;
  2. =和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式;
  3. 儘量選擇區分度高的列作爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄;
  4. 索引列不能參與計算,保持列“乾淨”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2014-05-29’);
  5. 儘量的擴展索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。

常用的套路是在定位到慢查詢語句之後,使用 EXPLAIN + SQL 來了解 MySQL 在執行這條數據時的一些細節,比如是否進行了優化、是否使用了索引等等。基於 Explain 的返回結果我們就可以根據 MySQL 的執行細節進一步分析是否應該優化搜索、怎樣優化索引。

就在這幾天,美團技術團隊開源了一款用於分析如何優化 SQL 的工具——SQLAdvisor,有興趣可以試試。

優化索引也並不是萬能的,這種情況下只能考慮通過其他方式來緩和性能上的瓶頸了。

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