數據庫和操做系統同樣,是一個多用戶使用的共享資源。當多個用戶併發地存取數據 時,在數據庫中就會產生多個事務同時存取同一數據的狀況。若對併發操做不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。加鎖是實現數據庫並 發控制的一個很是重要的技術。在實際應用中常常會遇到的與鎖相關的異常狀況,當兩個事務須要一組有衝突的鎖,而不能將事務繼續下去的話,就會出現死鎖,嚴 重影響應用的正常執行。
在數據庫中有兩種基本的鎖類型:排它鎖(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的獨佔鎖而沒法上升的獨佔鎖也就不可能釋放共享鎖,因而出現了死鎖。這種死鎖比較隱蔽,但在稍大點的項 目中常常發生。如在某項目中,頁面上的按鈕點擊後,沒有使按鈕馬上失效,使得用戶會屢次快速點擊同一按鈕,這樣同一段代碼對數據庫同一條記錄進行屢次操 做,很容易就出現這種死鎖的狀況。
解決方法:
一、對於按鈕等控件,點擊後使其馬上失效,不讓用戶重複點擊,避免對同時對同一條記錄操做。
二、使用樂觀鎖進行控制。樂觀鎖大可能是基於數據版本(Version)記錄機制實現。即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是 經過爲數據庫表增長一個「version」字段來實現。讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數 據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。樂觀鎖機制避免了長事務中的數據 庫加鎖開銷(用戶A和用戶B操做過程當中,都沒有對數據庫數據加鎖),大大提高了大併發量下的系統總體性能表現。Hibernate 在其數據訪問引擎中內置了樂觀鎖實現。須要注意的是,因爲樂觀鎖機制是在咱們的系統中實現,來自外部系統的用戶更新操做不受咱們系統的控制,所以可能會造 成髒數據被更新到數據庫中。
三、使用悲觀鎖進行控制。悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,如Oracle的Select … for update語句,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。如一個金融系統, 當某個操做員讀取用戶的數據,並在讀出的用戶數據的基礎上進行修改時(如更改用戶帳戶餘額),若是採用悲觀鎖機制,也就意味着整個操做過程當中(從操做員讀 出數據、開始修改直至提交修改結果的全過程,甚至還包括操做員中途去煮咖啡的時間),數據庫記錄始終處於加鎖狀態,能夠想見,若是面對成百上千個併發,這 樣的狀況將致使災難性的後果。因此,採用悲觀鎖進行控制時必定要考慮清楚。
死鎖的第三種狀況
若是在事務中執行了一條不知足條件的update語句,則執行全表掃描,把行級鎖上升爲表級鎖,多個這樣的事務執行後,就很容易產生死鎖和阻塞。相似的情 況還有當表中的數據量很是龐大而索引建的過少或不合適的時候,使得常常發生全表掃描,最終應用系統會愈來愈慢,最終發生阻塞或死鎖。
解決方法:
SQL語句中不要使用太複雜的關聯多表的查詢;使用「執行計劃」對SQL語句進行分析,對於有全表掃描的SQL語句,創建相應的索引進行優化。
5.小結
整體上來講,產生內存溢出與鎖表都是因爲代碼寫的很差形成的,所以提升代碼的質量是最根本的解決辦法。有的人認爲先把功能實現,有BUG時再在測試階段進 行修正,這種想法是錯誤的。正如一件產品的質量是在生產製造的過程當中決定的,而不是質量檢測時決定的,軟件的質量在設計與編碼階段就已經決定了,測試只是 對軟件質量的一個驗證,由於測試不可能找出軟件中全部的BUG。數據庫