技術分享 | MySQL 行鎖超時排查方法優化

作者:xuty

本文來源:原創投稿
* 愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。

一、大綱

#### 20191219 10:10:10,234 | com.alibaba.druid.filter.logging.Log4jFilter.statementLogError(Log4jFilter.java:152) | ERROR |  {conn-10593, pstmt-38675} execute error. update xxx set xxx = ? , xxx = ?  where RowGuid = ?
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

之前在 [如何有效排查解決 MySQL 行鎖等待超時問題] 文章中介紹瞭如何監控解決行鎖超時報錯,當時介紹的監控方案主要是以 shell 腳本 + general_log 來捕獲行鎖等待信息,後來感覺比較麻煩,因此優化後改成用 Event + Procedure 的方法定時在 MySQl 內執行,將行鎖等待信息記錄到日誌表中,並且加入了 pfs 表中的事務上下文信息,這樣可以省去登陸服務器執行腳本與分析 general_log 的過程,更加便捷。

因爲用到了 Eventperformance_schema 下的系統表,所以需要打開兩者的配置,pfs 使用默認監控項就可以,這裏主要使用到的是 events_statements_history 表,默認會保留會話 10 條 SQL。

performance_schema = on
event_scheduler = 1 


二、步驟

目前該方法僅在 MySQL 5.7 版本使用過,MySQL 8.0 未測試。

2.1 創建庫

create database `innodb_monitor`;

2.2 創建存儲過程

use innodb_monitor;
delimiter ;;
CREATE PROCEDURE pro_innodb_lock_wait_check()
BEGIN
 declare wait_rows int;
 
set group_concat_max_len = 1024000;

CREATE TABLE IF NOT EXISTS `innodb_lock_wait_log` (
  `report_time` datetime DEFAULT NULL,
  `waiting_id` int(11) DEFAULT NULL,
  `blocking_id` int(11) DEFAULT NULL,
  `duration` varchar(50) DEFAULT NULL,  
  `state` varchar(50) DEFAULT NULL,
  `waiting_query` longtext DEFAULT NULL,
  `blocking_current_query` longtext DEFAULT NULL,
  `blocking_thd_last_query` longtext,
  `thread_id` int(11) DEFAULT NULL
);

 select count(*) into wait_rows from information_schema.innodb_lock_waits ;

 if wait_rows > 0 THEN 
   insert into `innodb_lock_wait_log`  SELECT now(),r.trx_mysql_thread_id waiting_id,b.trx_mysql_thread_id blocking_id,concat(timestampdiff(SECOND,r.trx_wait_started,CURRENT_TIMESTAMP()),'s') AS duration,
     t.processlist_command state,r.trx_query waiting_query,b.trx_query blocking_current_query,group_concat(left(h.sql_text,10000) order by h.TIMER_START DESC SEPARATOR ';\n') As blocking_thd_query_history,thread_id
    FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id
     LEFT JOIN performance_schema.threads t on t.processlist_id = b.trx_mysql_thread_id LEFT JOIN performance_schema.events_statements_history h USING(thread_id) group by thread_id,r.trx_id order by r.trx_wait_started;
 
 end if;
END
;;

2.3 創建事件

事件 每隔 5 秒 (通常等於 innodb_lock_wait_timeout 的值)執行一次,持續監控 7 天,結束後會自動刪除事件,也可以自定義保留時長。

use innodb_monitor;
delimiter ;;
CREATE EVENT `event_innodb_lock_wait_check`
ON SCHEDULE EVERY 5 SECOND 
 STARTS CURRENT_TIMESTAMP 
 ENDS CURRENT_TIMESTAMP + INTERVAL 7 DAY
ON COMPLETION NOT PRESERVE
ENABLE
DO
call pro_innodb_lock_wait_check();
;;

2.4 事件啓停

--1爲全局開啓事件,0爲全局關閉
mysql > SET GLOBAL event_scheduler = 1;

--臨時關閉事件
mysql > ALTER EVENT event_innodb_lock_wait_check DISABLE;

--關閉開啓事件
mysql > ALTER EVENT event_innodb_lock_wait_check  ENABLE;


三、日誌表

再根據應用日誌報錯時間點及 SQL 分析 innodb_lock_wait_log 表。其中主要有 2 種場景:

  1. blocking_current_query 不爲空,說明阻塞事務處於運行狀態,這時候需要分析當前運行 SQL 是否存在性能問題。
  2. blocking_current_query 爲空,state 爲 Sleep,此時阻塞事務處於掛起狀態,即不再運行 SQL,此時需要通過分析 blocking_thd_last_query 分析事務上下文,注意該列中的 SQL 爲時間降序,即從下往上執行。


文章推薦:

技術分享 | 大量 Opening tables 案例分析

故障分析 | 崩潰恢復巨慢原因分析

技術分享 | MySQL 內存管理初探




社區近期動態




本文關鍵字: #鎖# #排錯分析# #MySQL 5.7#
  點一下“閱讀原文”瞭解更多資訊

本文分享自微信公衆號 - 愛可生開源社區(ActiontechOSS)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

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