db2 避免死鎖和鎖等待
資料庫中之所以會存在死鎖或者鎖等待,是因為某一事務執行時間過長,導致鎖沒有及時釋放,那麼我們的解決辦法就是,事務過程儘量要短,並且事務中的sql執行要快,這樣才不會有過多的鎖等待。還有一個原因,就是一些執行糟糕的sql,比如走了全表掃描,那麼它會佔據表中大量的鎖,導致鎖住了其他行,其他使用者只能等待。
解決鎖等待,要注意以下幾點:
Ø 優化查詢 Sql,採用db2advis建立合適的索引,使得其能夠走索引查詢,由於索引的範圍和排序,可以直接跳過許其他行,定位到符合我們需要的行。
Ø 採用合適的隔離級別。由於DB2 預設是CS的隔離級別,它的原理是,遊標每到一行就會鎖住改行,對於一般應用來說是足夠了,但是如果遇到全表掃描,那麼CS模式會鎖住表中大量的行,直到查詢完畢。所以可以根據業務需求,將其改為UR模式,它不會對錶加任何行鎖。或者在JDBC中設定隔離級別(Isolation Levels)
Ø 合理設定鎖超時引數,它主要是用來避免事務長時間被佔用,導致鎖和連線無法釋放,影響系統的併發。可以設定 DB引數
Ø 更新操作一定要走索引,否則很容易產生死鎖。(針對邊更新邊查的操作)
Ø 避免出現鎖升級現象,當鎖等待達到一定程度時(行鎖的個數超過loctList *percent of lock list),就會出現行鎖升級為表鎖,即鎖升級。因為一旦出現鎖升級,那麼鎖住的就不再是行,而是表,那麼其他事務要想訪問該表中的任意行,必須等待事務將鎖釋放。
修改Lock timeout (-1代表不檢測鎖超時),一般來說,該引數預設為10s足矣。
當系統存在嚴重的鎖等待時,可以通過以下sql,定位到鎖等待Sql
db2 "select AGENT_ID ,substr(STMT_TEXT,1,100) as statement,STMT_ELAPSED_TIME_MS from table(SNAPSHOT_STATEMENT('dbname',-1)) as B where AGENT_ID in (select AGENT_ID_HOLDING_LK from table(SNAPSHOT_LOCKWAIT(‘dbname’,-1)) as A order by LOCK_WAIT_START_TIME ASC FETCH FIRST 20 ROWS ONLY ) order by STMT_ELAPSED_TIME_MS DESC"
執行結果如下:
死鎖比鎖超時更加可怕,因為它將隨機回滾一個事務,而這個不受應用程式控制,不可控的錯誤十分可怕,所以一旦出現死鎖,必須解決掉。如何觀察DB2是否存在死鎖呢,有以下兩種方式:
1) 開啟lock快照監控
db2 update monitor switches using LOCK on
執行如下命令:
db2 get snapshot for database on dbname | grep -i "LOCK" ,結果如下:
可以看到其中有DeadLocks detected
2) 採用db2top 工具(db2 V9.1後才有)
Db2top –d dbname
然後鍵盤輸入‘d’,如下:
可以看到其中有個 DeadLocks 計數。
如何定位死鎖也有很多方式,如:
1) 建立死鎖監控器(需要針對檔案分析,複雜度較高)
2) 採用db2pd(最穩定,一般可以定位到)
3) 採用db2top 監控元件(最快,但不一定能抓到)
附件中,講述方法2),該方法我在差旅測試過程中發現,並且總是可以精確定位到。
附上:如何精確定位死鎖
——誰佔有鎖,誰等待鎖,供開發參考
本次差旅發現過很多死鎖,有很多死鎖定位方式。但是能精確定位的還是比較少。通過本次差旅實踐,發現通過dbpd來捕捉是最好的,也是最精確的。方法我總結如下:
1) 啟用死鎖監控
db2pdcfg –catch deadlock
當死鎖觸發時,會自動執行db2cos指令碼(在%db2dump%/bin 目錄下)。這個腳本里呼叫了db2pd來將當前資訊捕捉下來,其中主要捕捉的資訊包含如下:
Db2pd共捕捉了locks(鎖)、transaction(事務)、agents(代理程序)、application(應用)、dynamic(動態sql,這個最重要,是用於定位到sql)
2) 坐等死鎖。當死鎖觸發時,將自動生成 db2cos.process_id_application_id.txt,該檔案在db2的診斷目錄下(可以通過 db2 get dbm cfg | grep “DIA”來獲取診斷目錄位置)
3) 分析db2cos檔案:
a) 首先看-locks showlocks 模組,截圖如下:
找到其中包含【Sts】為W*,並且根據【ReleaseFlg】定位到相同slot的,類似,總共會有兩處,一處狀態為 W*(是死鎖)、一處是 G(代表鎖擁有)。Slot相同,就代表他們是在同一個資料物件上等待。尋找後的資料,如下截圖:
從上圖,可以看出如下資訊:
Ø 在tabled=514和tabspacesId=4上等待(可以在syscat.tables 檢視上根據資訊定位到表名稱 select TABNAME , TABSCHEMA from syscat.tables where TABLEID=514 and TBSPACEID=4)
Ø 佔據表鎖的transactionHandle Id 為 28,是X排他鎖
Ø 等待表鎖的transactionHandle Id為55,是NS(下一鍵共享鎖)
有了這兩個Id接著往下走。我們已經知道了誰在等待、誰在佔有。
b) 檢視-transaction,獲取transaction id對應的agent id
可以看到Apphandle對應值應有了,下面就需要根據這個Apphandle去找sql執行的資訊,已經離目標不遠了哈。
c) 檢視-dynamic 資訊
觀察如下列【C-AnchID】、【C-StmtUID】、【L-AnchID】、【L-StmtUID】
其中C-AnchID是代表當前正在執行的sql槽號,L-AnchID是代表上次執行的sql槽號。OK,我們就需要通過這個槽號來找到對應的Sql。定位到如下模組:
然後根據【C-AnchID】、【C-StmtUID】、【L-AnchID】、【L-StmtUID】列值找到對應sql(AnchID->C- AnchID,StmtUID-> C-StmtUID),如下:
自此,我們發現了導致死鎖的Sql:
哪個sql 在等待鎖?
狀態為W*:
SELECTCOUNT ( * )
FROM BIZ.WF_TASK A
INNERJOIN
BIZ.REI_FORM B
ON A.RECEIPT_NO = B.REI_FORM_ID
WHERE A.TASK_STATUS = ?
AND A.HANDLE_ID = ?
AND A.RECEIPT_TYPE = ?
AND B.APPROVE_STATUS = ?
AND B.REI_FORM_NO = ?
哪個Sql在佔有鎖?
狀態為G:
UPDATE BIZ.REI_FORM
SET APPROVE_STATUS = ?
WHERE REI_FORM_ID = ?
整個過程截圖如下:
定位到Sql之後,我們就可以按照第三節中敘述的方法,該建索引就建立索引,sql寫的不規範就調整sql。