效能問題案例02——sybase串連阻塞問題,02sybase
現象:最近現場反饋一個問題,系統在審批的時候,經常卡死,整個系統完全用不了,瀏覽器訪問處於loading的狀態。
排查:
1.一般系統掛了首先想到記憶體問題,但是現象是loading,也就是說沒有掛,線程正在執行,懷疑是線程被阻塞了,配置上jvisualvm監控了一下,出問題後記憶體沒滿確定不是記憶體問題,查看線程dump發現大部分都在執行sql查詢,
初步發現是執行sql慢導致的。
2.我們用的是sybase資料庫,執行了幾個簡單sql發現幾分鐘都沒執行完,使用sp_sysmon "00:00:30"監控近30秒的情況發現cpu、記憶體、線程都沒問題,幾乎1%使用率都不到,懷疑是某個串連阻塞了表,導致其他串連全部阻塞導致的。
3.我們使用自己寫的預存程序查看阻塞的串連,結果如下:
PS:使用sp_lock命令就可以查看哪個串連阻塞了資料庫,但是顯示的都是tableid等,還需要重新查詢轉換成具體表名等,自己寫的預存程序只是此處轉換了一下,後面會附上。
發現其中1萬多個鎖,7000多個排它鎖,Ex_row-blk是阻塞了其他串連的鎖,發現有21個阻塞了其他串連的鎖,對應的表是T_ZXLD_SYYH,執行select * from master..sysprocesses找到對應的串連,如的User有316/287/283等,結果如下:
此時基本確定了原因,我們用的是c3p0串連池,應該是某個串連阻塞了表,其他所有串連查詢時候都被阻塞了,導致串連池被佔滿,所有請求凡是涉及資料庫查詢的都被阻塞了,頁面始終處於loading狀態。
4.那麼接下來就是找到阻塞的地方,發現tran_name都是$chained_transaction,結合程式判斷,也就是說都在執行某個責任鏈裡面的事務時阻塞了,系統就2處使用了責任鏈,直接就可以判斷到時審批的責任鏈導致的,那麼接下來就是排查此處代碼是否有問題了,審批次程序如下:
(1)開啟事務
(2)根據參數查詢出要審批的主表資料,2個sql
(3)逐條調用審批組件(類似工作流程的一個組件)審批,每條資料大約5個更新sql,2個查詢sql
(4)更新主表狀態,每條資料1個更新sql
(5)插入審計日誌,每條資料1個插入sql
(6)產生提醒訊息,查詢全部主表資料,20個查詢sql,5個更新刪除sql(非常慢)
(7)更新增量記錄表,記錄該條資料修改時間和狀態等,每條資料1個更新sql
(8)提交事務
懷疑並發時相互阻塞導致的,產生提醒訊息的地方,會查詢全部業務表,如果此時有其他串連在事務中審批,就會阻塞,其他串連在產生訊息,相互阻塞,造成死結。理論上資料庫會自動處理死結的,但是不知道什麼原因,日誌報的死結數量不是特別多。
而且此處業務處理也太合理,正常使用者每次批量審批大約100條資料,所以大約有900多個增刪改sql,200多個查詢sql,像產生訊息等sql執行非常慢(因為涉及更新舊訊息),這麼多操作放到一個事務中非常慢,審批表大約3000w資料,業務主表500w資料,執行起來也不是很快。
解決辦法:重構了此處代碼,講上面執行慢的(5)(6)(7)步驟新起一個線程,不放到事務中執行,即使失敗也影響不大。這樣sql減少了30%,速度快了不少,更新到現場,觀察了一個星期,發現未出現不響應等情況,算是問題解決。
雖然問題解決,但是根本原因沒有找到,為什麼會相互阻塞,為什麼死結沒自動檢測,這個是後續需要跟蹤的。
最後,問題雖然解決了,但是中間溝通花了不少時間,和營運人員要現場資料耽誤了很多事,簡單整理一下,後續出問題的時候搜集這幾個資料:
1、sp__lock的結果;(使用文章最後提供的預存程序)
2、select * from master..sysprocesses;
3、如果資料庫慢,加上sp_sysmon "00:00:30"的結果;
4、中介軟體線程dump檔案;
附上sybase查看鎖的預存程序
IF OBJECT_ID ('dbo.sp__lock') IS NOT NULLDROP PROCEDURE dbo.sp__lockGOcreate procedure sp__lock( @dbname char(30)=null,@spid int=null, @dont_format char(1) = null)asbegin declare @dbid smallint if @dbname is not null select @dbid=db_id(@dbname) if (charindex("sa_role", show_role()) > 0) begin if @dont_format is null select "Type"=substring(v.name,1,11), "User"=substring(suser_name(p.suid)+" ("+rtrim(convert(char(6),l.spid))+")",1,20), "Table"=substring(db_name(l.dbid)+".."+convert(char(20),object_name(l.id,l.dbid)),1,26), "Page"=convert(char(8),l.page), "Cmd"=substring(p.cmd,1,11) from master..syslocks l, master..sysprocesses p, master..spt_values v where p.spid=l.spid and l.type = v.number and v.type = "L" and p.dbid=isnull(@dbid,p.dbid) and p.spid=isnull(@spid,p.spid) and l.dbid=isnull(@dbid,l.dbid) and l.spid=isnull(@spid,l.spid) order by l.dbid, l.id, v.name else select "Type"=v.name, "User"=suser_name(p.suid)+" ("+rtrim(convert(char(6),l.spid))+")", "Table"=db_name(l.dbid)+".."+object_name(l.id,l.dbid), "Page"=l.page, "Cmd"=p.cmd from master..syslocks l, master..sysprocesses p, master..spt_values v where p.spid=l.spid and l.type = v.number and v.type = "L" and p.dbid=isnull(@dbid,p.dbid) and p.spid=isnull(@spid,p.spid) and l.dbid=isnull(@dbid,l.dbid) and l.spid=isnull(@spid,l.spid) order by l.dbid, l.id, v.name return end select "Type"=v.name, "Usernm"=convert(varchar(60),suser_name(p.suid)+" ("+rtrim(convert(char(6),l.spid))+")"), "TableNm"=convert(varchar(60),db_name(l.dbid)+".."), "Page"=l.page, "Cmd"=p.cmd, l.id, l.dbid into #locks from master..syslocks l, master..sysprocesses p, master..spt_values v where p.spid=l.spid and l.type = v.number and v.type = "L" and l.dbid=isnull(@dbid,l.dbid) and l.spid=isnull(@spid,l.spid) and p.dbid=isnull(@dbid,p.dbid) and p.spid=isnull(@spid,p.spid) update #locks set TableNm=TableNm+object_name(id,dbid) where dbid=db_id() or dbid=1 or dbid=2 update #locks set TableNm=TableNm+convert(varchar,id) where dbid<>db_id() and dbid>2 delete #locks where TableNm like "tempdb..#locks%" if @dont_format is null select substring(Type, 1,11), "User"=substring(Usernm, 1,14), "Table"=convert(char(26),TableNm), "Page"=convert(char(8),Page), "Cmd"=substring(Cmd,1,11) from #locks order by dbid, id, Type else select Type, "User"=Usernm, "Table"=TableNm, Page, Cmd from #locks order by dbid, id, TypeendGO