相信我們在進行測試的時候,有的時候會遇上數據庫表的值插不進去的情況,在執行SQL語句的時候,好像卡住一樣,沒有反應。但是當你把SQL語句copy下來放在PL/SQL中執行的時候,在語法沒有任何的錯誤,但是執行會很慢,此時我們就應該要想到應該表被鎖了,尤其是當多條語句不停的提交,不停的執行的時候,這個時候就會由於語句執行不完整,而容易引起鎖表。
下面先介紹一下表的鎖已經在設計是應該注意的問題。
在數據庫中有兩種基本的鎖類型:排它鎖(Exclusive Locks,即X鎖)和共享鎖(Share Locks,即S鎖)。當數據對象被加上排它鎖時,其他的事務不能對它讀取和修改。加了共享鎖的數據對象可以被其他事務讀取,但不能修改。數據庫利用這兩種基本的鎖類型來對數據庫的事務進行並發控制。
死鎖的第一種情況:
一個用戶A 訪問表A(鎖住了表A),然后又訪問表B;另一個用戶B 訪問表B(鎖住了表B),然后企圖訪問表A;這時用戶A由於用戶B已經鎖住表B,它必須等待用戶B釋放表B才能繼續,同樣用戶B要等用戶A釋放表A才能繼續,這就死鎖就產生了。
解決方法:
這種死鎖比較常見,是由於程序的BUG產生的,除了調整的程序的邏輯沒有其它的辦法。仔細分析程序的邏輯,對於數據庫的多表操作時,盡量按照相同的順序進行處理,盡量避免同時鎖定兩個資源,如操作A和B兩張表時,總是按先A后B的順序處理, 必須同時鎖定兩個資源時,要保證在任何時刻都應該按照相同的順序來鎖定資源。
死鎖的第二種情況:
用戶A查詢一條紀錄,然后修改該條紀錄;這時用戶B修改該條紀錄,這時用戶A的事務里鎖的性質由查詢的共享鎖企圖上升到獨占鎖,而用戶B里的獨占鎖由於A有共享鎖存在所以必須等A釋放掉共享鎖,而A由於B的獨占鎖而無法上升的獨占鎖也就不可能釋放共享鎖,於是出現了死鎖。這種死鎖比較隱蔽,但在稍大點的項目中經常發生。如在某項目中,頁面上的按鈕點擊后,沒有使按鈕立刻失效,使得用戶會多次快速點擊同一按鈕,這樣同一段代碼對數據庫同一條記錄進行多次操作,很容易就出現這種死鎖的情況。
解決方法:
1、對於按鈕等控件,點擊后使其立刻失效,不讓用戶重復點擊,避免對同時對同一條記錄操作。
2、使用樂觀鎖進行控制。樂觀鎖大多是基於數據版本(Version)記錄機制實現。即為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個“version”字段來實現。讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大於數據庫表當前版本號,則予以更新,否則認為是過期數據。樂觀鎖機制避免了長事務中的數據庫加鎖開銷(用戶A和用戶B操作過程中,都沒有對數據庫數據加鎖),大大提升了大並發量下的系統整體性能表現。hibernate 在其數據訪問引擎中內置了樂觀鎖實現。需要注意的是,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的用戶更新操作不受我們系統的控制,因此可能會造成臟數據被更新到數據庫中。
3、使用悲觀鎖進行控制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,如Oracle的Select … for update語句,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。如一個金融系統,當某個操作員讀取用戶的數據,並在讀出的用戶數據的基礎上進行修改時(如更改用戶賬戶余額),如果采用悲觀鎖機制,也就意味着整個操作過程中(從操作員讀出數據、開始修改直至提交修改結果的全過程,甚至還包括操作員中途去煮咖啡的時間),數據庫記錄始終處於加鎖狀態,可以想見,如果面對成百上千個並發,這樣的情況將導致災難性的后果。所以,采用悲觀鎖進行控制時一定要考慮清楚。
死鎖的第三種情況 :
如果在事務中執行了一條不滿足條件的update語句,則執行全表掃描,把行級鎖上升為表級鎖,多個這樣的事務執行后,就很容易產生死鎖和阻塞。類似的情況還有當表中的數據量非常龐大而索引建的過少或不合適的時候,使得經常發生全表掃描,最終應用系統會越來越慢,最終發生阻塞或死鎖。
解決方法:
SQL語句中不要使用太復雜的關聯多表的查詢;使用“執行計划”對SQL語句進行分析,對於有全表掃描的SQL語句,建立相應的索引進行優化。
解鎖:
用dba權限的用戶登陸數據庫。
1、select * from v$locked_object 查出被鎖定的對象,其中object_id是對象的ID,session_id是被鎖定對象有session ID;
2、select object_name, object_type from dba_objects where object_id = 剛才查出來的object_id;(根據v$locked_object里的object_id提出來的)
這樣來查被鎖定這個對象的名字,如果能確定是哪個TABLE被鎖並且要解鎖,則再執行
3、select sid, serial#, machine, program from v$session where sid =第1步中查出來的session_id;(是根據v$locked_object對應鎖定記錄的session_id找出來的) 然后
4、alter system kill session 'sid,serial#';用來殺死這個會話;
以上幾個步驟即能解決對象被鎖定問題。
另外附上鎖定一個表的語句:
LOCK TABLE tablename IN EXCLUSIVE MODE;將鎖定整個表