目錄
一.故障現象... 1
二.初步分析... 2
三.排障過程... 2
1.排查是否QPS或insert並發請求上升導致問題發生... 2
2.排查是否鎖資源等待或block導致了insert變慢... 3
3.排查是否表上無用索引導致的寫入時間較長... 5
4、人工抓取perf,排查CPU上升期間的資源消耗... 5
5、疑似觸發MySQL BUG,進一步分析... 6
四.優化過程... 8
1.初步優化方案... 8
2.刪除一批無用索引,將服務器內存升級到80G.. 9
3.未達預期,還需繼續優化... 11
4.熱表索引分析... 11
5.隨機GUID建立索引的性能測試... 13
6.熱表索引優化方案... 14
7.前綴索引的性能測試... 14
8.刪除熱表上非順序的二級索引... 16
五.最終優化方案... 17
六.總結... 18
一.故障現象
有台生產服務器間歇性CPU飆升,出現大量insert語句的慢查詢,相關業務的響應時間隨之大幅上升
二.初步分析
從監控報告來看,這台服務器的負載並不高
消耗時間高的SQL是insert系列語句
三.排障過程
1.排查是否QPS或insert並發請求上升導致問題發生
排查並發請求沒並有突然升高,反而在問題時間段先大幅下降再小幅上升,這個現象說明MySQL在問題時間段的處理能力發生了下降
表的insert並發頻率並沒有大的波動
2.排查是否鎖資源等待或block導致了insert變慢
以一句慢查詢insert into為例,查詢SQL執行的明細記錄,這句SQL的執行時間在異常時間點達到12秒,對應locktime只有63微秒,排除了表鎖等待,排查問題發生過程中,rowlock相關指標沒有大幅上升,排除rowlock等待,也沒有明顯的block產生,正常執行時<3毫秒。
3.排查是否表上無用索引導致的寫入時間較長
我們都知道表上大量的無用索引不僅浪費存儲空間,也會增加數據寫入的成本,因此在測試環境新建了相同的表,保留索引不變,測試索引維護成本的消耗
看到這句insert into正常執行時的各階段的消耗,總體執行時間不到2ms
4、人工抓取perf,排查CPU上升期間的資源消耗
參考命令如下,
注意:下面命令在生產上執行時有較低概率會導致服務器hang死
#生成mysql進程10秒內資源消耗采樣報告
sudo perf record -p `pidof mysqld` -g -o /tmp/perf.data sleep 10
#查看報告
sudo perf report -i /tmp/perf.data
CPU資源消耗占比較高的是ibuf_get_volume_buffered_count_func函數,它主要有2個功能,一是統計change buffer中對於同一page ,buffer了多少空間,二是在准備插入類型為IBUF_OP_DELETE的操作緩存時,會預估在apply完該page上所有的ibuf entry后還剩下多少記錄。
5、疑似觸發MySQL BUG,進一步分析
通過網上搜索,了解到有相關的BUG
該BUG的鏈接:https://bugs.mysql.com/bug.php?id=77827
下面是BUG描述
MySQL對每個表對象獨立分配rw lock,當開啟change buffer時,Innodb會頻繁的創建dummy table(一種用於線程私有的簡單的索引結構),這種dummy index事實上無需使用states_latch,因為他是線程私有的;但mysql沒有做區分,在創建rw lock時,會加全局鎖rw_lock_list_mutex來維護全局讀寫鎖鏈表rw_lock_list。
也看到Ali關於這個問題的分析,同時也發現幾個關聯的BUG,直到MySQL5.7.6版本,問題才完全修復
四.優化過程
1.初步優化方案
1、 從以上分析來看,這個問題的產生與業務大量的二級索引頻繁更新是有關系的,目前DB下共有5094個索引。因此我們決定先刪除大量無用的索引,看效果是否明顯
2、還有個導致問題的原因是內存遠小於數據集,計划將innodb_buffer_pool_size從64G擴充到70G-90G之間
2.刪除一批無用索引,將服務器內存升級到80G
將這台服務器的內存升到80G后,對比了這個集群昨天和今天的運行情況。
總的來看,增加內存后,CPU波動有所緩解,CPU的峰值和高消耗持續的時間有所降低,運行時的並發線程數也有降低,但問題並沒有根本解決。
這台服務器(QPS約4000)的change buffer使用情況,緩存最大時達到23G(說明有大量的二級索引在寫入時需要加入緩存進行合並),但是按照目前80G的buffer pool,change buffer的最大值只有20G,仍有瓶頸
對比另外寫入頻繁的(QPS約9000)一台服務器change buffer的使用情況,最大只有256K
3.未達預期,還需繼續優化
評估下來后,需要繼續刪除熱表的索引,下面對熱表的索引情況做了進一步分析
4.熱表索引分析
分析DB下熱表的時候發現每次CPU飆升,邏輯IO消耗時間較長的表排名TOP 2都是固定的兩張表,約占了所有表邏輯IO消耗時間的80%。
這兩張表有什么特征會影響到邏輯IO的消耗時間,下面其中一張表為例
表結構中有一個row_key的字段,和開發確認,這個字段存儲的是隨機的GUID值,對應這個字段上建立了一個索引idx_row_key。
我們知道Innodb的聚集索引和二級索引都是一顆B+樹,row_key字段建立索引,在插入數據維護索引時,以row_key值的大小做為頁和記錄的排序規則,隨着大量並發隨機GUID值的插入,
為了保持B+樹的平衡,新插入的數據可能會帶來大量的頁拆分的操作,這時change buffer起到了關鍵的優化作用,將二級索引的操作緩存下來,並進行操作合並,減少二級索引的隨機IO。
這兩個表的容量使用情況,記錄數達到3億條,二級索引占用了較多的容量
5.隨機GUID建立索引的性能測試
在測試環境中模擬類似的場景,使用sysbench並發256線程進行壓測
結論:
1、 隨着表的記錄數的增多,當達到千萬級以上的記錄數時,隨機GUID字段上的二級索引維護開銷很明顯,對插入性能的影響逐漸增大(從開始的30K的QPS下降到約3K)。
2、 刪除GUID字段的二級索引后,QPS處理能力大幅上升,恢復到40K的QPS
6.熱表索引優化方案
1、 從索引使用統計來看這張表上的idx_row_key索引實際並沒有使用過,如果能直接刪除,優化效果預計會比較明顯
2、 如果業務邏輯上row_key的索引確實需要,折中的辦法可以嘗試創建前綴索引
對隨機GUID值的前8個字符創建索引,這樣只在B+樹中存儲字符串的前幾個字符的編碼,能節約一部分空間,減少字符串的比較時間,在一定程度上緩解排序和頁拆分的問題,語法如下:
ALTER TABLE table1 ADD INDEX idx_row_key_prefix(row_key(8));
3、 業務上修改邏輯,將完全隨機的GUID生成規則改為順序的GUID生成規則
7.前綴索引的性能測試
下面測試表的數據約9000W,建立了兩個隨機GUID字段的前11個字符的前綴索引,QPS穩定在約10K左右。
8.刪除熱表上非順序的二級索引
觀察一天下來,CPU高消耗的問題基本消除。
當前change buffer的使用有較大幅度的減少,與刪除索引前相比降低了約74%
五.最終優化方案
將兩張大表改造為以時間字段為分區函數的分區表,分區表只保留最近30天的數據,改造完成后,從change buffer的使用來看,已經降低到16K
六.總結
對於記錄數多的大表,表上如果存在隨機的GUID字段或非順序的字符串字段,如果這些類型上建立二級索引,對於頻繁的增刪改操作,會帶來較高的維護成本。
當change buffer使用頻繁,空間很大時,服務器性能也會出現大幅下降。這時我們可以通過刪除熱表的二級索引,改造分區表,清理大表數據,OPTIMIZE TABLE等操作來進行優化。