本文由做者鄭智輝受權網易雲社區發佈。html
本文經過分析線上MySQL慢查詢日誌,定位出現問題的SQL,進行業務場景分析,結合索引的相關使用進行數據庫優化。在兩次處理問題過程當中,進行的思考。mysql
在九月底某個新上的遊戲業務MySQL慢查詢日誌算法
# Time: 2017-09-30T14:56:13.974292+08:00 # Query_time: 6.048835 Lock_time: 0.000038 Rows_sent: 0 Rows_examined: 12884410SET timestamp=1506754573;SELECT status, sdkid, appid, app_orderid, matrix_orderid, pay_orderid, platform, sdk_version, app_channel, pay_channel, serverid, roleid, INET6_NTOA(userip), deviceid, devic e_name, productid, product_count, product_name, matrix_uid, app_uid, order_currency, order_price, activityid, create_time, expired_time, pay_method, pay_mode, ship_url, rese rved, pay_time, recv_time, ship_time, pay_sub_method, pay_amount, free_amount, pay_currency, pay_total_money, pay_free_money, credit, pay_fee, extra_columns, is_test FROM MatrixOrderSucc WHERE status >= 200 AND status < 300 AND recv_time < DATE_SUB(NOW(), interval 20 SECOND) AND recv_time > DATE_SUB(NOW(), interval 24 HOUR) ORDER BY retry LIMIT 1;
第一次處理方式:在該表上添加了(recv_time,status)索引,而後慢查詢沒有;sql
正當覺得事情解決的時候,該遊戲10月份大推,而後數據量激增,而後慢查詢又出現了。數據庫
第二次處理方式:刪除以前的索引,而後改成對(status,recv_time)添加索引。而後至今該SQL未出現慢查詢了。數據結構
線上環境說明:app
MySQL 5.7.18ide
表引擎爲Innodb性能
系統內核:Debian 3.16.43-2測試
接下來講說這兩次處理過程當中的測試和分析。
sql分析:
當時九月底時該表的數據達到1200w行,可是因爲沒有匹配得上的索引,因此全表掃描耗時6秒多。
業務分析:
聯繫了開發同事,瞭解一下這個語句的業務場景。 該語句用於查找失敗訂單(status標記)而且時間在20秒以前一天之內(recv_time)的數據。並得知其實知足status條件的訂單其實只是少許的。
小結:
能夠看出數據和固定時間範圍內的數據量有關係。10月份大推後,固定時間範圍內的數據激增。
將數據導到測試環境進行了數據測試。
經過下圖的sql,數據基本分析以下:
* 知足單獨status條件的數據大概就3w條 * 知足單獨recv_time條件的數據大概是77w條 * 雖然status字段的數據離散型不是很好,可是知足條件的數據不多,數據的篩選性仍是很不錯的。
加了索引以後。(recv_time,status)
mysql> explain select status, sdkid, appid, app_orderid, matrix_orderid, pay_orderid, platform, sdk_version, app_channel, pay_channel, serverid, roleid, INET6_NTOA(userip), deviceid, device_name, productid, product_count, product_name, matrix_uid, app_uid, order_currency, order_price, activityid, create_time, expired_time, pay_method, pay_mode, ship_url, reserved, pay_time, recv_time, ship_time, pay_sub_method, pay_amount, free_amount, pay_currency, pay_total_money, pay_free_money, credit, pay_fee, extra_columns, is_test from MatrixOrderSucc WHERE status >= 200 AND status < 300 AND recv_time < DATE_SUB('2017-10-12 14:48:49', interval 20 SECOND) AND recv_time > DATE_SUB('2017-10-12-14:48:49', interval 24 HOUR); +----+-------------+-----------------+------------+-------+---------------+-----------+---------+------+---------+----------+-----------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------------+------------+-------+---------------+-----------+---------+------+---------+----------+-----------------------+ | 1 | SIMPLE | MatrixOrderSucc | NULL | range | recv_time | recv_time | 6 | NULL | 1606844 | 11.11 | Using index condition | +----+-------------+-----------------+------------+-------+---------------+-----------+---------+------+---------+----------+-----------------------+1 row in set, 1 warning (0.00 sec)
執行計劃:剛加上的索引確實被用上了。
正式環境臨時添加了該索引以後慢查詢確實消失了。
從執行計劃裏的key_len能夠知道該sql,在進行數據篩選的時候只以recv_time進行數據過濾的,status字段並無用上場。由於聯合索引左側字段用了範圍查詢,則其餘字段沒法用上。
背景知識 數據查找過程:1. 若是走了輔助索引* 先去輔助索引查找。返回索引字段和主鍵字段(index_column, pk column),假設數據N行,那麼這裏是N次的數據順序訪問* 再去彙集索引查找整行數據:N次隨機訪問 數據搜索代價:b+樹高度次隨機訪問+N次順序訪問+N次隨機訪問。 ps:固然若是輔助索引能覆蓋了SQL查詢的字段,就不須要去主表查完整整行數據了。 2.若是直接全表掃描: 數據搜索代價:全表總數次順序訪問 磁盤順序訪問和隨機訪問時間消耗大概查了兩個數量級。 因此有可能:MySQL會估算一下,二者的代價來決定是否走索引查找。
因此上面的sql在mysql 5.6以前執行過程:
經過recv_time條件在輔助索引搜索,返回N條記錄
彙集索引查找整行數據
返回到server 段而後再進行status字段的條件篩選
server層返回數據給客戶端
然而,MySQL 5.6以後多了index condition push down的優化功能,就是能將索引篩選下推。
例如:
執行計劃裏的Using index condition是index push down的意思,是mysql 5.6後作的優化,
這個功能的效果就是,能將步驟3的數據篩選放在步驟2以前,由於既然從輔助索引取回的數據包含status字段,那麼進行一下數據過濾,而後再去主表拿數據,就能減小隨機訪問的次數。
10月遊戲大推每日數據激增。此時全表數據大概2800w。
再去經過explain 查看執行計劃的時候,已經從原來的走索引,又變回了全表掃描。
慢查詢的時間從以前的6秒上升到18秒
爲何以前走索引如今會不走了?
有同事說:在應用層 force index強制走以前的索引就行了。由於多是MySQL的優化器優化得不夠好。致使走了不良的執行計劃。 我認爲:這個問題和應用問題和MySQL優化關係不大,是索引建得不對。若是在應用層作修改,第一須要通過測試迴歸才能發佈版本,耗時長;第二,force index 感受比較死板,萬一之後表結構發生變動,這個索引不存在了,會存在問題。
線上數據分析:
單獨知足recv_time條件的數據達到600多萬行。(由於遊戲大推,每日數據激增),原來只有77w行。
單獨知足status條件的數據變化不大。
MySQL採用全表掃描的結論:
由於輔助索引返回的數據激增,致使主表隨機訪問的次數增長,發現還不如直接全表掃描來得快。
固然MySQL的SQL優化代價模型應該包含不少因素,後續有待研究。
仍是利用以前導出的1200w的測試數據,對(status,recv_time)條件索引進行測試。
經過下圖能夠看到:
查詢能走上索引,而且key_len=10,代表索引的兩列都派上用上了。
而且執行計劃裏的rows數量明顯比(recv_time,status)索引的查詢要少不少。
個人理解:
1.status雖然在sql裏看起來是範圍查詢,可是MySQL能感知到status數據的離散程度,而後將status查詢改成IN(200),IN在MySQL裏不算範圍查詢。
2.其實這個挺好理解的。結合索引的B+樹的結構。 若是是IN,至關於在輔助索引裏經過第一列得出的是N個B+子樹(以第二索引字段進行構建的子樹),那麼確定仍是能夠對第二列進行二叉樹搜索的。
因此關鍵就是在第一列搜索完後,剩下的數據是否能對第二列recv_time進行二叉樹搜索。
由於recv_time真的是足夠離散。
在索引選擇,在有(recvtime,status) (status,recvtime) (status)三個索引下
KEY `status` (`status`,`recv_time`), KEY `status_2` (`status`), KEY `recv_time` (`recv_time`,`status`) mysql> explain SELECT count(*) FROM MatrixOrderSucc WHERE status >= 200 AND status < 300 AND recv_time < DATE_SUB('2017-10-12 14:48:49', interval 20 SECOND) AND recv_time > DATE_SUB('2017-10-12 14:48:49', interval 24 HOUR); +----+-------------+-----------------+------------+-------+---------------------------+--------+---------+------+-------+----------+--------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------------+------------+-------+---------------------------+--------+---------+------+-------+----------+--------------------------+ | 1 | SIMPLE | MatrixOrderSucc | NULL | range | status,status_2,recv_time | status | 10 | NULL | 58650 | 8.94 | Using where; Using index | +----+-------------+-----------------+------------+-------+---------------------------+--------+---------+------+-------+----------+--------------------------+1 row in set, 1 warning (0.00 sec)
能夠看出系統選擇了(status,recv_time)索引。
所以在正式環境刪除以前的索引,建新的索引,慢查詢消失。
5.1 不是離散性很差的字段就不能加索引,也要看數據篩選性能
5.2 時間類型的字段不大合適放在聯合索引的左邊
5.3 索引最左匹配原則 5.4 測試說明
5.4.1 數據是經過mysqldump不加鎖方式導到測試環境從新import創建的。
5.4.2 測試的SQL:最好不要選select count() from table ,由於在這個場景中select count() 會走索引掃描,是沒必要再到主表拿整行數據的;和實際場景的SQL是不同。
參考文檔
更多網易技術、產品、運營經驗分享請訪問網易雲社區。
相關文章:
【推薦】 Spring-Boot自定義Starter實踐