informix 數據庫鎖表分析和解決方法


一、前言

在聯機事務處理(OLTP)的數據庫應用系統中,多用戶、多任務的並發性是系統最重要的技術指標之一。為了提高並發性,目前大部分RDBMS都采用加鎖技術。
然而由於現實環境的復雜性,使用加鎖技術又不可避免地產生了死鎖問題。因此如何合理有效地使用加鎖技術,最小化死鎖是開發聯機事務處理系統的關鍵。

二、死鎖產生的原因

在聯機事務處理系統中,造成死機主要有兩方面原因。

 

一方面,由於多用戶、多任務的並發性和事務的完整性要求,當多個事務處理對多個資源同時訪問時,若雙方已鎖定一部分資源但也都需要對方已鎖定的資源時,無法在有限的時間內完全獲得所需的資源,就會處於無限的等待狀態,從而造成其對資源需求的死鎖。
另一方面,數據庫本身加鎖機制的實現方法不同,各數據庫系統也會產生其特殊的死鎖情況。如在Sybase SQL Server 11中,最小鎖為2K一頁的加鎖方法,而非行級鎖。如果某張表的記錄數少且記錄的長度較短(即記錄密度高,如應用系統中的系統配置表或系統參數表就屬於此類表),被訪問的頻率高,就容易在該頁上產生死鎖。

2.1 容易發生死鎖的幾種情況如下:

1>不同的存儲過程、觸發器、動態SQL語句段按照不同的順序同時訪問多張表; 
2>在交換期間添加記錄頻繁的表,但在該表上使用了非群集索引(non-clustered); 
3>表中的記錄少,且單條記錄較短,被訪問的頻率較高; 
4>整張表被訪問的頻率高(如代碼對照表的查詢等)。

 

2.2 以上死鎖情況的對應處理方法如下:

1>在系統實現時應規定所有存儲過程、觸發器、動態SQL語句段中,對多張表的操作總是使用同一順序。如:有兩個存儲過程proc1、proc2,都需要訪問三張表zltab、z2tab和z3tab,如果proc1按照zltab、z2tab和z3tab的順序進行訪問,那么,proc2也應該按照以上順序訪問這三張表。 

2>對在交換期間添加記錄頻繁的表,使用群集索引(clustered),以減少多個用戶添加記錄到該表的最后一頁上,在表尾產生熱點,造成死鎖。這類表多為往來賬的流水表,其特點是在交換期間需要在表尾追加大量的記錄,並且對已添加的記錄不做或較少做刪除操作。 

3>對單張表中記錄數不太多,且在交換期間select或updata較頻繁的表可使用設置每頁最大行的辦法,減少數據在表中存放的密度,模擬行級鎖,減少在該表上死鎖情況的發生。這類表多為信息繁雜且記錄條數少的表。

如:系統配置表或系統參數表。在定義該表時添加如下語句: 
with max_rows_per_page=1 
在存儲過程、觸發器、動態SQL語句段中,若對某些整張表select操作較頻繁,則可能在該表上與其他訪問該表的用戶產生死鎖。對於檢查賬號是否存在,但被檢查的字段在檢查期間不會被更新等非關鍵語句,可以采用在select命令中使用at isolation read uncommitted子句的方法解決。

該方法實際上降低了select語句對整張表的鎖級別,提高了其他用戶對該表操作的並發性。在系統高負荷運行時,該方法的效果尤為顯著。 
如: 
select * from titles at isolation read uncommitted 
對流水號一類的順序數生成器字段,可以先執行updata流水號字段+1,然后再執行select獲取流水號的方法進行操作。

http://www.blogjava.net/imdosop/archive/2008/11/06/239085.html

2.3數據庫鎖表的分析與解決

上面介紹了內存溢出的原因和處理方法,下面再介紹一下數據庫鎖表及阻塞的原因和處理辦法。
數據庫和操作系統一樣,是一個多用戶使用的共享資源。當多個用戶並發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的情況。若對並發操作不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。加鎖是實現數據庫並發控制的一個非常重要的技術。在實際應用中經常會遇到的與鎖相關的異常情況,當兩個事務需要一組有沖突的鎖,而不能將事務繼續下去的話,就會出現死鎖,嚴重影響應用的正常執行。
在數據庫中有兩種基本的鎖類型:排它鎖(Exclusive Locks,即X鎖)和共享鎖(Share Locks,即S鎖)。當數據對象被加上排它鎖時,其他的事務不能對它讀取和修改。加了共享鎖的數據對象可以被其他事務讀取,但不能修改。數據庫利用這兩種基本的鎖類型來對數據庫的事務進行並發控制。


2.3.1死鎖的第一種情況

一個用戶A 訪問表A(鎖住了表A),然后又訪問表B;另一個用戶B 訪問表B(鎖住了表B),然后企圖訪問表A;這時用戶A由於用戶B已經鎖住表B,它必須等待用戶B釋放表B才能繼續,同樣用戶B要等用戶A釋放表A才能繼續,這就死鎖就產生了。
解決方法:
這種死鎖比較常見,是由於程序的BUG產生的,除了調整的程序的邏輯沒有其它的辦法。仔細分析程序的邏輯,對於數據庫的多表操作時,盡量按照相同的順序進行處理,盡量避免同時鎖定兩個資源,如操作A和B兩張表時,總是按先A后B的順序處理, 必須同時鎖定兩個資源時,要保證在任何時刻都應該按照相同的順序來鎖定資源。

2.3.2死鎖的第二種情況

用戶A查詢一條紀錄,然后修改該條紀錄;這時用戶B修改該條紀錄,這時用戶A的事務里鎖的性質由查詢的共享鎖企圖上升到獨占鎖,而用戶B里的獨占鎖由於A有共享鎖存在所以必須等A釋放掉共享鎖,而A由於B的獨占鎖而無法上升的獨占鎖也就不可能釋放共享鎖,於是出現了死鎖。這種死鎖比較隱蔽,但在稍大點的項目中經常發生。如在某項目中,頁面上的按鈕點擊后,沒有使按鈕立刻失效,使得用戶會多次快速點擊同一按鈕,這樣同一段代碼對數據庫同一條記錄進行多次操作,很容易就出現這種死鎖的情況。
解決方法:
1、對於按鈕等控件,點擊后使其立刻失效,不讓用戶重復點擊,避免對同時對同一條記錄操作。
2、使用樂觀鎖進行控制。樂觀鎖大多是基於數據版本(Version)記錄機制實現。即為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個“version”字段來實現。讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大於數據庫表當前版本號,則予以更新,否則認為是過期數據。樂觀鎖機制避免了長事務中的數據庫加鎖開銷(用戶A和用戶B操作過程中,都沒有對數據庫數據加鎖),大大提升了大並發量下的系統整體性能表現。Hibernate 在其數據訪問引擎中內置了樂觀鎖實現。需要注意的是,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的用戶更新操作不受我們系統的控制,因此可能會造成臟數據被更新到數據庫中。

3、使用悲觀鎖進行控制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,如Oracle的Select … for update語句,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。如一個金融系統,當某個操作員讀取用戶的數據,並在讀出的用戶數據的基礎上進行修改時(如更改用戶賬戶余額),如果采用悲觀鎖機制,也就意味着整個操作過程中(從操作員讀出數據、開始修改直至提交修改結果的全過程,甚至還包括操作員中途去煮咖啡的時間),數據庫記錄始終處於加鎖狀態,可以想見,如果面對成百上千個並發,這樣的情況將導致災難性的后果。所以,采用悲觀鎖進行控制時一定要考慮清楚。

2.3.2死鎖的第三種情況

如果在事務中執行了一條不滿足條件的update語句,則執行全表掃描,把行級鎖上升為表級鎖,多個這樣的事務執行后,就很容易產生死鎖和阻塞。類似的情況還有當表中的數據量非常龐大而索引建的過少或不合適的時候,使得經常發生全表掃描,最終應用系統會越來越慢,最終發生阻塞或死鎖。
解決方法:
SQL語句中不要使用太復雜的關聯多表的查詢;使用“執行計划”對SQL語句進行分析,對於有全表掃描的SQL語句,建立相應的索引進行優化。

2.3小結

總體上來說,產生內存溢出與鎖表都是由於代碼寫的不好造成的,因此提高代碼的質量是最根本的解決辦法。有的人認為先把功能實現,有BUG時再在測試階段進行修正,這種想法是錯誤的。正如一件產品的質量是在生產制造的過程中決定的,而不是質量檢測時決定的,軟件的質量在設計與編碼階段就已經決定了,測試只是對軟件質量的一個驗證,因為測試不可能找出軟件中所有的BUG。

三、鎖表處理步驟

1、onstat -ks|grep HDR+X //查詢是哪個表被鎖
address wtlist owner lklist type tblsnum rowid key#/bsiz c1809510 0 d656e774 c181cb3c HDR+X 6002e1 2c602 0 需要關注lklist和type項,從上面來看tblsnum為6002e1(6292193十六進制轉換成十進制)的表被鎖了。可以重查詢是那個表被鎖: dbaccess :select * from systables where partnum='6292193'得到 tabname basetab_mvpn owner smpmml partnum 6292193 tabid 12813 rowsize 464 ncols 61 nindexes 1 nrows 2984 created 12/10/2002 version 839843846 tabtype T locklevel R npused 746 fextsize 16 nextsize 16 flags 0 2、onstat -u,將owner(address)為d656e774的線程找出來 address flags sessid user tty wait tout locks nreads nwrites d656e774 Y--P--- 4261 smp20 - d6ad2330 0 180 99620 16 3、onstat -g sql d656e774可以將這個線程執行過的sql語句打印出來。 4、只要用informix用戶執行onmode-z sessid干掉線程 onmode-z 4261 重點說明:onstat -g ses sessid找個進程PID來,然后ps -ef|grep Pid; kill -9 pid 在處理這些問題時還會遇到表被鎖是因為該線程還沒有執行完畢,此時就不能簡單的 onmode -z殺線程 復制代碼

四、Informix殺掉死鎖的方法和查看死鎖的sql語句

Informix鎖表產生的原因,要么是多個用戶同時訪問數據庫導致該問題,要么是因為某個進程死了以后資源未釋放導致的。如果是前一種情況,
可以考慮將 數據庫表的鎖級別改為行鎖,來減少撞鎖的機會;或在應用程序中,用set lock mode wait 3這樣的語句,在撞鎖后等待若干秒重試。
如果是后一種情況,可以在數據庫端用onstat -g ses/onstat -g sql/onstat -k等命令找出鎖表的進程,用onmode -z命令結束進程;如果不行,
就需要重新啟動數據庫來釋放資源。

1:$ onstat -k | grep HDR+X

獲得sessid,其中HDR+X 為排他鎖,HDR 頭,X 互斥 ,sessid 是會話標識符編號。 2:$ onstat -g ses sessid 根據sessid得到進程pid,其中pid 是與此會話的前端關聯的進程標識。 (可通過$ onstat -g sql sessid 命令查看執行的sql語句) 3:$ ps -ef |grep pid 由此,我們可得到鎖表的進程,可根據實際鎖表進程的重要程度的具體情況采取相映處理方法: 對於重要且該進程可以自動重聯數據庫的進程,可以用onmode -z sesid 的方法殺掉鎖表session: $ onmode –z sessid 否則也可直接殺掉鎖表的進程 kill pid: $ kill -9 pid 至此,死鎖即被清除掉。 ————————————————————————- 查找鎖定的表名稱 通過onstat -k 查找的rowid 等於0的表鎖信息的 tblsnum 信息查找表名。 如 tblsnum等於500e19 執行 select * from systables where hex(partnum)=’0×00500e19′ 查找到當前鎖表的表名。 Informix -244 錯誤 : Could not do a physical-order read to fetch next row. 具體錯誤解釋: #finderr -244 原因: a.鎖表 b.記錄太多 c.頁損壞 d.某個進程死了以后資源未釋放導致 在數據庫端用 onstat –g ses/onstat –g sql / Onstat –k 等找出鎖表進程,用onmode –z結束該進程, 不行,重啟數據庫釋放。 鎖方式: 行方式(row),頁方式(默認page),表方式(table)。 解決: 1.降低鎖級別 2.減少加鎖事務的時間跨度 3.設置等待解瑣時間 相關命令: )檢查索引及頁損壞情況 #oncheck –cID database_name:table_name ) 查看鎖級別 #oncheck –pt database_name:table_name )設置鎖級別(行方式) #alter table table_name lock mode(row) )設置隔離級別 #set isolation to dirty read )設置等待解鎖時間(不宜過大) #set lock mode to wait second(秒) 不等待 #set lock mode to not wait 還可以這樣解決: 如果是日志型數據庫,在執行的時候,可以先鎖定 begin work; lock table tab_name in exclusive mode; 要執行的sql語句; commit work; 如果是非日志的數據 lock table table_name in exclusive mode; 要執行的sql語句; unlock table tab_name; 中間件全局事務鎖問題 中間件與informix連接時會出現onstat -k 中 owner 內容為0的現象。 這是應該查看全局事務onstat -x 和onstat -G DATABASE sysmaster; select hex(tx_addr) trans_addr,hex(tx_lklist) lock_addr from systrans where hex(tx_addr) like '%c00000006e329778%'; 需要說明的是,c000000007674c58是使用onstat -x 或 onstat -G得到的全局事務的地址。 上面SQL語句提供出該全局事務對應的鎖地址,這時如果得到的鎖地址與鎖表的鎖地址相同的話, 你就必需從應用端(通常是三層結構的中間件)發命令讓該全局事務回滾或提交,否則該鎖會被一直持有,直到你執行oninit。 onstat -k 的owner 列中的地址與onstat -x 中的userthread 對應

轉自:https://www.cnblogs.com/kongzhongqijing/articles/4462765.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM