資料庫卡頓急救指南:搞懂會話狀態跟排查「殭屍行程」實戰

在上一篇文章裡面呢,我們其實已經把作業系統的那個障礙給跨過去了,客戶端到伺服器也就算是連通了。那麼問題來了,只要你的資料庫開始跑真實的業務,各種奇怪的毛病往往僅僅只是會跟著跑出來。
通常來說的話,日常維運裡面最讓人頭痛的告警,其實也就是「系統卡頓」了,或者說「資料庫連線池被打滿」的情況。遇到這種緊急的事兒啊,很多新手的直接反應是什麼呢?重啟應用唄,或者更粗暴一點,直接把資料庫服務給重啟了。這其實是不對的。
那麼作為一個搞資料庫維運的人,我們其實得像醫生那樣,透過系統層面的工具去把病因給找出來。這篇文章的話,我就會帶著大家深入到電科金倉資料庫的底層監控台裡面去,手把手教你搞懂系統的會話狀態。也就是說,我們要把那些佔著資源不幹活的長交易,還有殭屍行程給揪出來,接著再把它幹掉。
@[toc]
sys_stat_activity 檢視表當業務線的同學跑過來跟你抱怨,說「資料庫沒回應了」的時候。你第一件要做的事,其實絕對不是去翻業務日誌。那該幹嘛呢?也就是說,你得趕緊登入到資料庫裡面去,看看它這個時候到底在忙啥。
在金倉資料庫裡面的話,有一個非常核心的動態系統檢視表,那就是 sys_stat_activity。這個檢視表的話,它其實就像是資料庫的一個監控螢幕一樣,每個連進來的客戶端正在幹啥,它都會即時給你記下來。
那麼你打開本地的終端機,比如 cmd 視窗,接著用 ksql 這個命令列工具去登入資料庫,然後執行下面這段全盤掃描的 SQL 就行了:
sql 体验AI代码助手 代码解读复制代码SELECT
pid,
usename,
client_addr,
state,
query_start,
query
FROM sys_stat_activity
WHERE usename IS NOT NULL;

那麼我們挑幾個最核心的欄位,用大白話來給大家翻譯一下:
pid(程序 ID): 這個的話,其實就是資料庫給這個連線分配的一個作業系統程序號,它是唯一的。比如你截圖裡面看到的那個 20758 啊,還有 12127,你先把它記下來,這可是我們等會兒要去處理的目標。usename(使用者名稱): 這個看名字就知道了,是誰發起的連線。目前顯示的都是 system,也就是說,這是管理員自己人在操作。client_addr(客戶端位址): 發起連線的那台機器的 IP 位址。比如 PID 是 20758 的那一行,它就顯示了具體的客戶端 IP。那如果遇到那種海量的異常連線的情況,你在這裡就能直接看到是哪個網段搞的鬼了。state(目前狀態): 這個欄位的話,可以說是最重要的了!它是判斷你這個連線到底健不健康的唯一標準。在截圖裡面,你可以很清楚地看到 active,也就是活躍的狀態,還有 idle,也就是閒置的狀態。query_start(SQL 開始時間): 這條 SQL 是什麼時候開始跑的?這個東西的話,其實就是我們用來抓那些「長查詢」的一個時間參考點。query(執行語句): 這個程序最後執行的,或者說正在執行的那個 SQL 文本。你注意看截圖裡面,PID 是 20758 的那一行,它的 query 裡面顯示的內容,恰好就是我們剛剛敲進去的這句 SELECT 查詢語句本身!這也就證明了,我們的監控是即時在跑的。state —— 揪出真正搞鬼的傢伙監控螢幕上那些密密麻麻的程序,其實並不全都是壞東西。那麼在 sys_stat_activity 的 state 欄位裡面的話,我們通常來說需要學會去分辨下面這幾種核心的狀態:
active(正在執行)也就是說,這個連線它現在正在那老老實實地跑 query 欄位裡面的 SQL 語句,就像截圖裡那個 PID 20758 那樣。
active 狀態的 SQL 跑了幾十分鐘還沒完,那就是典型的「慢查詢」了。它在瘋狂吃你的 CPU 和 IO 資源,這時候你就得去看看它的執行計畫了。idle(閒置)這個狀態的話,意思是客戶端連著資料庫,但是目前啥活都沒幹,就像截圖裡面那個 PID 12127 那樣。
idle 狀態的長連線在那隨時待命。只要總連線數沒爆滿的情況,你就不用去管它。idle in transaction(交易中閒置)—— ⚠️ 核心警報!這個的話,就是我們常說的那種佔著資源不幹活的典型代表了!它的意思是,客戶端開了一個交易,可能執行了 BEGIN 或者關了自動提交,接著跑了幾條 SQL 之後,它既沒有 COMMIT,也沒有 ROLLBACK,就這麼一直掛在那邊了。
日常巡檢的時候呢,我們其實不需要像剛才那樣看滿屏的資料。你要幹的僅僅只是把執行時間超過 1 分鐘的 active 語句給抓出來,還有把所有的 idle in transaction 程序給找出來就行。那麼你可以把下面這段 SQL 收藏起來,當成你的 DBA 必殺技:
sql 体验AI代码助手 代码解读复制代码SELECT
pid,
usename,
client_addr,
state,
now() - query_start AS duration,
query
FROM sys_stat_activity
WHERE state IN ('active', 'idle in transaction')
AND state IS NOT NULL
AND (now() - query_start) > interval '1 minute';
光看理論其實沒用,既然手頭有 ksql 環境,那麼我們就可以自己來造一個 idle in transaction 的案發現場看看。
cmd 視窗進去 ksql(這就相當於模擬那個出 Bug 的業務系統):建一張測試表,接著開一個交易,去執行一下更新操作,但是記住千萬別提交!sql 体验AI代码助手 代码解读复制代码-- 建表並插入一筆資料
CREATE TABLE test_lock (id int, name varchar(50));
INSERT INTO test_lock VALUES (1, '金倉護航者');
-- 開啟交易,執行更新,但不做 COMMIT!
BEGIN;
UPDATE test_lock SET name = '殭屍資料' WHERE id = 1;
cmd 視窗進去 ksql(這就相當於我們 DBA 巡檢的視角了):在這裡面,我們再去執行一下前面說的那個全盤掃描 SQL,或者說你那個 DBA 必殺技 SQL 也行。
那麼你在右側的這個 DBA 監控視圖裡面,就能看得很清楚了。PID 是 20758 的那個連線,正是因為我們在左邊敲了 BEGIN 並且沒提交,所以這個時候它的 state 就變成了那個讓人頭痛的 idle in transaction 了!而且的話,它最後執行的那句 UPDATE 語句,也被我們監控視圖給扒出來了。
既然抓到了搞鬼的傢伙,也就是那個 PID: 20758,接著就是去處理它了。
這裡一定要記住:千萬別去 CentOS 作業系統的終端機裡面,用 kill -9 去殺資料庫的實體程序! 這麼搞的話,極容易把共享記憶體給搞壞,甚至會讓整個資料庫執行個體直接當掉重啟的。
金倉資料庫它自己在內部就提供了一個很好用的內建函式,那就是:sys_terminate_backend(pid)。
那麼在你右側的這個 DBA 監控視窗裡面,對著剛才找到的那個殭屍行程 PID 20758,去執行下面這個強制切斷的命令:
sql 体验AI代码助手 代码解读复制代码-- 這裡的 20758 就是我們在監控視圖中抓到的異常程序 PID
SELECT sys_terminate_backend(20758);
執行完了之後,它會返回一個 t (true),這就說明處理成功了。 
到了這個時候,那個被卡住的鎖就已經被資料庫給強制放開了,沒提交的髒資料也會自動回滾掉。那之前被卡住的其他業務,往往僅僅只是瞬間就能恢復順暢了。
手動殺程序的話雖然很爽,但是 DBA 也不能天天盯著螢幕當監工對吧。如果業務線新來的開發同學程式碼寫得不行,經常忘了寫 commit 的情況,那該怎麼辦呢?
其實我們可以從資料庫底層下手,去設一個全域的防線:也就是交易閒置超時自動斷開。
你去打開伺服器端的資料目錄,接著修改 kingbase.conf 這個設定檔就行:
ini 体验AI代码助手 代码解读复制代码# 找到或手動新增該參數(單位為毫秒)
# 例如設定為 60000(即 60 秒),表示只要交易處於 idle in transaction 狀態超過 60 秒,資料庫直接幫你把這個會話踢掉。
idle_in_transaction_session_timeout = 60000
(註:改完了之後,記得在 Linux 終端機去執行一下 sys_ctl reload ,讓設定熱載入生效哈)
配了這個參數之後的話,也就相當於給所有的資料庫連線加了個倒數計時,再也不用怕寫得爛的程式碼把整個資料庫執行個體給拖垮了。
結語
那麼透過上面對 sys_stat_activity 的拆解和實作,我們其實也就掌握了資料庫效能排障的第一項核心技能,也就是會話狀態的判斷和精準干預。
但是在真實的複雜業務裡面的話,卡頓往往不僅僅只是「忘了提交」這麼簡單。有時候啊,幾個正常的業務程序,它們會因為搶同一張表的修改權而互相卡死,這就變成了那種很隱蔽的「列鎖等待」了。
在下一篇文章裡面呢,我們會接著往資料庫的微觀世界裡面走,手把手教你去搞定那種更複雜的 「列鎖衝突與等待事件」。也就是說,讓你真正具備從根源上診斷,還有優化高併發業務的能力。大家就期待一下吧!