自動記錄MySQL慢查詢快照指令碼

weixin_33766168發表於2016-11-21

Python完成的一個小程式,用於殺掉 MySQL 上的異常執行緒,如慢查詢、處於Sleep狀態的。
專案地址https://github.com/seanlook/myquerykill

寫這個指令碼的初衷是在使用阿里雲RDS的過程中,資料庫出現異常,需要快速恢復。網上有許多類似的kill指令碼,都是通過 mysqladmin 實現的。然而 Ali-RDS 環境有以下限制:

  • 不提供 SUPER 許可權的使用者,也就是使用者只能 kill 自己的執行緒
  • 當連線數暴增時,外部使用者無法登陸,包括控制檯

為了解決上午2大問題,該 python 指令碼通過在db例項上,使用多執行緒的方式,為每個使用者保留一個連線,並實時讀取指令配置檔案 mysqk.ini,發現有 kill 需求時,利用對應使用者已有連線找到 information_schema.processlist 中符合條件的執行緒,並 kill 。

說明:該指令碼在9月份做過一次重寫,7月份的版本(分支 old_0.5.0)是每例項每使用者,對應一個執行緒,db例項一多執行緒數也太多,看得始終不太優雅,於是改成了一個db例項一個執行緒,維護同時維護多個使用者的會話。同時新版也加入了更多的功能,如按時間視窗檢查,包含或排除特定連線,郵件通知,配置項覆蓋。

特性

  1. 始終通過 mysql ping 維持一個長連線,並有斷開自動重來機制,解決沒有連線可用的尷尬局面
  2. 每個db例項有自己的執行緒,避免需要單獨登陸個別使用者去kill的繁複操作。
    如果你具有 SUPER 許可權,也可以簡化配置做到相容
  3. 能夠分開應對需要殺死執行緒的場景:

    • 長時間執行超過 N 秒的
    • Sleep 狀態的事務 (一般不建議,但有時候kill它,可以快速釋放連線給管理員使用)
    • 排除一些執行緒不能kill,如 Binlog dump
    • 包含特定關鍵字的執行緒要kill
  4. 出現符合條件的執行緒時,會對當時的processlist, engine status,lock_wait 做一個快照,並郵件發出
  5. 有試執行dry_run模式,即執行所有的檢查過程但不真正kill
  6. 支援只在時間視窗內執行,考慮到晚上一些長任務不檢查
  7. 密碼加密

快速使用

需要pip安裝MySQL-pythonpycrypto兩個庫,只在python 2.7上有測試。

settings.py 裡面設定連線的使用者名稱和密碼資訊。這裡假設同一批db的要check的認證資訊是一樣的,指定的使用者既用於登入認證,也用於告知指令碼哪些使用者需要被檢查。
密碼要通過 prpcryptec.py 加密,加密的金鑰需寫入指令碼本身的 KEY_DB_AUTH變數。(擔心洩露的話,把mysqk.py編譯成 pyc 來跑)

mysqk.ini 主配置檔案裡面

  • db_info 節設定需要被檢查的資料庫地址,如 db01=10.0.200.100:3306
  • 可分別 db01等指定需要kill thread的選項。[id_db01] 則預設複用 [db_commkill] 的選項
  • db_comconfig 節設定 db_puser 為能檢視到所有processlist的許可權使用者,且在 settings.py 的DB_AUTH中已指定
  • 只想執行檢查,並不想真正kill異常執行緒,確認 dry_run不等於0

Here we go!

配置項說明

mysqk.ini

mail_config

郵件通知相關設定,smtp服務地址和認證資訊。
mail_receiver= 設定空,表示不發郵件

db_info

設定要檢查kill哪些資料庫例項.
格式:<dbid>=<host>:<port>,dbid是唯一表示db例項的,後面設定各db需要被kill的選項,小節配置名就是 id_<dbid>;埠必需指定。

在這裡出現的db例項都會被執行檢查,可用 ; 註釋,但需要重啟指令碼。

db_comconfig

檢查用公共配置,實時生效。

  • db_puser:指定一個使用者名稱用於 show processlist,需要的許可權:PROCESS、information_schema庫檢視。可以認為是一個代表使用者,檢查異常thread,把結果提供給有該thread殺掉許可權使用者。
  • run_max_count:執行檢查的次數,是一個全域性控制開關。每次修改這個值都會重新開始檢查,即一個 clean start,讓剛修改的配置生效。

    • 為 0 表示指令碼不進行任何檢查,只簡單維護與資料庫的連線存活。存活檢查頻率在 settings.pyCHECK_CONFIG_INTERVAL × CHECK_PING_MULTI決定
    • 為 999 表示會在後臺一致檢查連線執行緒(但不一定有符合kill條件的),檢查的頻率在 settings.py 裡面 CHECK_CONFIG_INTERVAL 指定
    • 為其它值時,表示檢查次數滿後停止檢查
  • dry_run:是否開啟試執行模式,為0表示真實kill,為1或其它值表示試執行。試執行模式可用於監控慢查詢並告警。注意同一會話執行緒ID只告警一次
  • run_time_window:執行的檢查的時間視窗,格式如 08:00-22:00,在這個時間以外不執行檢查,留空表示不限制。主要考慮晚上一些統計任務可能出現“異常”執行緒。

db_commkill

kill用公共配置,實時生效,會被 id_<dbid> 節的選項覆蓋。

  • k_user:很關鍵的一個選項,表示你要檢查並kill哪些資料庫使用者,多個用逗號分隔(不要帶引號)。
    all 時,表示要檢查 settings.py 裡 DB_AUTH 指定的所有使用者

none 時,表示不kill任何異常執行緒,效果與設定了 dry_run 模式相當

  • k_longtime:執行超過設定值的sql則認為異常。一般大於 CHECK_CONFIG_INTERVAL
  • k_sleep:Sleep超過設定秒的sql則認為異常,為 0 表示不殺掉sleep狀態的執行緒
  • k_exclude:排除掉那些特定關鍵字的執行緒,比如複製執行緒、管理員的連線等
  • k_include:包含這些特定關鍵字的執行緒,需要被kill。注意,它作用在滿足 k_user 和 k_exclude 的前提之下。
    k_exclude與k_include 的值是支援python re模組正則的格式,不要帶引號

id_dbid

這部分割槽域的配置項與 db_commconfig 相同,用於針對個別db的kill選項。

使用建議

兩種組合模式:

  1. 設定 dry_run=0,預設 k_user=none,當資料庫出現異常時,主動修改對應db的k_user值,動態kill
  2. 設定 dry_run=1,預設 k_user=all,相當於執行在daemon模式,有慢查詢則郵件通知,並且記錄下當時的資訊

當然你也可以dry_run=0k_user=all,讓程式一直在後臺跑並kill,但生產環境極不推薦。

有日誌和快照檔案可以檢視。

配置檔案示例

mysqlk.ini :

[mail_config]
mail_host=smtp.exmail.qq.com
mail_user=xxx@ecqun.com
mail_pass=xxxxxx

mail_receiver=

[db_info]
crm0=192.168.1.125:3306
crm1=192.168.1.126:3306
crm2=192.168.1.127:3306
crm3=192.168.1.128:3306
base=10.0.200.142:3306

[db_commconfig]
db_puser=ecuser

; how many kill times once this config file changed
; 0: DISABLE all kill
; 999: always kill threads that meet kill conditions
; default: 1
; can not be inherit
run_max_count=999
dry_run=1
run_time_window=08:00-22:00


[db_commkill]
k_user=all
k_longtime=10
k_lock=1
k_sleep=0

k_exclude=Binlog|ecdba|Daemon
k_include=select sleep\(17\)


[id_crm0]
; k_user: who's threads to be killed. use comma to separate
;         none: do not kill anyone's threads
;         all: kill all user's threads (with other where conditions)
; default: none
k_user=all

; k_longtime: filter the threads who's running time is longer than this
;             0: ignore the time > x  condition
; default: 10
k_longtime=10

; k_sleep: whether kill sleepd threads or not
;          0: do not kill command='Sleep' threads from processlist
;          when it set to 1, usually it's subset of k_longtime condition
; default: 0
k_sleep=0

[id_crm1]
k_user=ecuser
k_longtime=10
k_sleep=0

[id_crm2]
k_user=all
k_longtime=10
k_sleep=0

[id_crm3]

原文連結地址:http://seanlook.com/2016/09/27/python-mysql-querykill/


相關文章