天天看點

mysql 序列_記一次生産環境MySQL耗時問題追蹤實錄。

前語:不要為了讀文章而讀文章,一定要帶着問題來讀文章,勤思考

mysql 序列_記一次生産環境MySQL耗時問題追蹤實錄。

作者:VipAugus   來源:http://1t.click/kQz

# 大概過程

在測試環境Docker容器中,在跨程序調用服務的時候,A應用通過Dubbo調用B應用的RPC接口,發現B應用接口逾時錯誤,接着通過debug和日志,發現具體耗時的地方在于一句簡單SQL執行,但是耗時超過1000ms。

通過檢視資料庫的程序清單,發現是有死鎖鎖表了,很多程序狀态status處于'sending data',最後為鎖住的表添加索引,并且kill掉阻塞的請求,解除死鎖,服務速度恢複正常。

下面記錄的是大緻排查過程:

通過觀察業務代碼,确認沒有記憶體溢出或者其它事務問題,于是隻能考慮Docker環境的資料庫和jvm底層詳情了。

# 使用Druid監控SQL執行狀态

通過日志,發現有一句SQL嚴重逾時,一句簡單SQL,原本是批量插入多條記錄,為了定位問題,測試時Mybatis隻插入一條記錄,但即便如此,還是耗時10秒。

mysql 序列_記一次生産環境MySQL耗時問題追蹤實錄。

于是打算使用阿裡巴巴的資料庫連接配接池Druid進行監控,監控SQL效果如下:

mysql 序列_記一次生産環境MySQL耗時問題追蹤實錄。

在SQL監控Tab中,可以看到執行SQL的具體情況,包括某條SQL語句執行的時間(平均、最慢)、SQL執行次數、SQL執行出錯的次數等。

上面顯示的是正常情況下,時間機關是ms,正常的SQL一般在10ms之内,資料量大的控制在30ms之内,這樣使用者的使用體驗感才會良好。是以說之前的1000ms,是不可接受的結果。

# 通過JMC遠端監控Tomcat

JMC(java mission control)是jdk自帶的一個監控工具,在jdk的bin目錄下(java大法好,該目錄下有很多實用的工具)。

此處加了一個tomcat無驗證模式:

#在tomcat的conf目錄下的catalina.sh增加如下java啟動參數:-Dcom.sun.management.jmxremote=true-Dcom.sun.management.jmxremote.port=8888-Dcom.sun.management.jmxremote.ssl=false-Dcom.sun.management.jmxremote.authenticate=false-XX:+UnlockCommercialFeatures -XX:+FlightRecorder
           

下面是自己本地調試的截圖

mysql 序列_記一次生産環境MySQL耗時問題追蹤實錄。

然後打開jmc,建立一個JMX連接配接,輸入對應的ip和JMX端口。接着可以設定一段時間内的飛行監控,監測這一分鐘内jvm具體參數

當時調試的時候,發現記憶體使用、CPU占用率、線程狀态也挺正常的,沒有發現明顯的異常錯誤,效果如下圖:

mysql 序列_記一次生産環境MySQL耗時問題追蹤實錄。

唯一比較耗時的是在代碼tab頁中,當時發現了大量的I/O,比上圖的比例還高,當時大概占了80%,檢視調用樹,很多循環tcp socket連接配接,考慮到應用中本來就有很多需要io以及netty也需要tcp連接配接,是以大概排除了jvm虛拟機的問題,然後就去排查MySQL的問題。

# 排查MySQL

在了解MySQL鎖概念的時候,由于現在使用的比較多的是InnoDB,是以可以着重看看InnoDB鎖問題。推薦:面試官:說說你針對MySQL死鎖問題的分析思路。

# 直接執行SQL語句

通過DEBUG代碼,從mybatis中取出映射後的SQL語句,在MySQL客戶款直接執行SQL和Explain檢視執行計劃,速度都很快,排除了SQL語句的問題。推薦:我艹,MySQL資料量大時,delete操作無法命中索引。

# 檢視MySQL線程清單

show processlist;

br

mysql 序列_記一次生産環境MySQL耗時問題追蹤實錄。

從圖中可以看出,有些線程的狀态處于sending data,查閱資料:所謂的“Sending data”并不是單純的發送資料,而是包括“收集 + 發送 資料”。

然後後面一列info顯示的是具體資訊,是查詢用來生成主鍵ID的函數,之前速度都很快,為啥突然就這麼慢呢,于是回過頭去檢視該函數:

select next_value into ret_val from `xxx` where table_name=tableName for update;update `xxx` set    current_value=current_value+step,    next_value=next_value+step  where table_name=tableName;
           

select for update,給這個表加了排它鎖,阻止其它事務取得相同資料集的共享讀鎖和排他寫鎖,同時,這個序清單表中,用來檢索的字段沒有加索引,在InnoDB行鎖機制中:

mysql 序列_記一次生産環境MySQL耗時問題追蹤實錄。

由于MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,是以雖然是通路不同行的記錄,但是如果是使用相同的索引鍵(在我們的場景中,就是查詢時用到的table_name),是會出現鎖沖突的。

是以了解到其它團隊因為查詢這個表産生事務問題,造成死鎖,這個序清單被鎖住了。Java提升篇-事務隔離級别和傳播機制Java提升篇-事務隔離級别和傳播機制

由于這個自增序清單每個團隊都在使用,是以當時測試環境中,經常有dao層逾時錯誤,最終将這些阻塞的線程kill掉,為序清單加了索引,解決了問題。

# 小結

下次遇到MySQL執行耗時的情況,排除了代碼問題之後,要去看資料庫是否有死鎖的情況存在,觀察有沒有被阻塞的線程,排查被阻塞的線程具體info,定位到具體問題。

熱文推薦

有贊中台如何助力标準化業務?

我敢打賭絕大多數程式員沒有這麼深入研究過System.out.println()!

mysql 序列_記一次生産環境MySQL耗時問題追蹤實錄。