【原創】記一次MySQL大表高並發寫入引發CPU飆升的排障過程


 目錄

一.故障現象... 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等操作來進行優化。


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM