數據庫的鎖

轉載自https://www.cnblogs.com/xiaofengwang/p/11291944.html

 

鎖的概念:

首先我們先了解下什麼是數據庫鎖,

鎖是事務對某個數據庫中的資源(如表和記 錄)存取前,先向系統提出請求,封鎖該資源,

事務獲得鎖後,即取得對數據的控制權,在事務釋放它的鎖之前,其他事務不能更新此數據。當事務撤消後,釋放被 鎖定的資源。 

爲什麼要鎖?

    數據庫是一個多用戶使用的共享資源,比如一個用戶表 t_user,兩個瀏覽器前面的人登錄了同個一個賬號,把電話號碼改了。當多個用戶併發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的情況。若對併發操作不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性(髒讀,不可重複讀,幻讀等),可能產生死鎖。爲了解決這個問題,加鎖是一個非常重要的技術,對實現數據庫併發控制是一個好的方案。簡單說,當一個執行sql語句的事務想要操作表記錄之前,先向數據庫發出請求,對你訪問的記錄集加鎖,在這個事務釋放這個鎖之前,其他事務不能對這些數據進行更新操作。

數據庫鎖的分類:

樂觀鎖:

樂觀鎖不是數據庫自帶的,需要我們自己去實現。樂觀鎖是指操作數據庫時(更新操作),想法很樂觀,認爲這次的操作不會導致衝突,在操作數據時,並不進行任何其他的特殊處理(也就是不加鎖),而在進行更新後,再去判斷是否有衝突了。
通常實現是這樣的:在表中的數據進行操作時(更新),先給數據表加一個版本(version)字段,每操作一次,將那條記錄的版本號加1。也就是先查詢出那條記錄,獲取出version字段,如果要對那條記錄進行操作(更新),則先判斷此刻version的值是否與剛剛查詢出來時的version的值相等,如果相等,則說明這段期間,沒有其他程序對其進行操作,則可以執行更新,將version字段的值加1;如果更新時發現此刻的version值與剛剛獲取出來的version的值不相等,則說明這段期間已經有其他程序對其進行操作了,則不進行更新操作。
舉例:
下單操作包括3步驟:
1.查詢出商品信息

select (status,status,version) from t_goods where id=#{id}

2.根據商品信息生成訂單
3.修改商品status爲2

update t_goods

set status=2,version=version+1

where id=#{id} and version=#{version};

悲觀鎖:
與樂觀鎖相對應的就是悲觀鎖了。悲觀鎖就是在操作數據時,認爲此操作會出現數據衝突,所以在進行每次操作時都要通過獲取鎖才能進行對相同數據的操作,這點跟java中的synchronized很相似,所以悲觀鎖需要耗費較多的時間。另外與樂觀鎖相對應的,悲觀鎖是由數據庫自己實現了的,要用的時候,我們直接調用數據庫的相關語句就可以了。
說到這裏,由悲觀鎖涉及到的另外兩個鎖概念就出來了,它們就是共享鎖排它鎖。共享鎖和排它鎖是悲觀鎖的不同的實現,它倆都屬於悲觀鎖的範疇。

共享鎖:

又叫S鎖或者讀鎖,加了共享鎖的數據對象可以被其他事務讀取,但不能修改, 通常是該數據對象被讀取完畢,鎖立即被釋放

排他鎖:

又叫X鎖或者寫鎖,當數據對象被加上排它鎖時,一個事務必須得到鎖才能對該數據對象進行訪問,一直到事務結束鎖才被釋放。 在此之間其他的事務不能對它讀取和修改。

行鎖:
行鎖,由字面意思理解,就是給某一行加上鎖,也就是一條記錄加上鎖。
比如之前演示的共享鎖語句
SELECT * from city where id = "1"  lock in share mode; 
由於對於city表中,id字段爲主鍵,就也相當於索引。執行加鎖時,會將id這個索引爲1的記錄加上鎖,那麼這個鎖就是行鎖。

補充==>(代碼中的死鎖現象)

死鎖:

死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。

這些永遠在互相等待的進程稱爲死鎖進程

產生死鎖的必要條件:

1、互斥使用,即當資源被一個線程使用(佔有)時,別的線程不能使用 
2、不可搶佔,資源請求者不能強制從資源佔有者手中奪取資源,資源只能由資源佔有者主動釋放。 
3、請求和保持,即當資源請求者在請求其他的資源的同時保持對原有資源的佔有。 
4、循環等待,即存在一個等待隊列:P1佔有P2的資源,P2佔有P3的資源,P3佔有P1的資源。這樣就形成了一個等待環路。

死鎖產生的原因:

1) 系統資源的競爭

通常系統中擁有的不可剝奪資源,其數量不足以滿足多個進程運行的需要,使得進程在 運行過程中,會因爭奪資源而陷入僵局

2) 進程推進順序非法

進程在運行過程中,請求和釋放資源的順序不當,也同樣會導致死鎖。例如,併發進程 P1、P2分別保持了資源R1、R2,而進程P1申請資源R2,進程P2申請資源R1時,兩者都 會因爲所需資源被佔用而阻塞。

3)信號量使用不當也會造成死鎖。

進程間彼此相互等待對方發來的消息,結果也會使得這 些進程間無法繼續向前推進。

如何避免死鎖?

三種用於避免死鎖的技術:

1)加鎖順序:

一個線程需要一些鎖,那麼它必須按照確定的順序獲取鎖。它只有獲得了從順序上排在前面的鎖之後,才能獲取後面的鎖。

例如,線程2和線程3只有在獲取了鎖A之後才能嘗試獲取鎖C(獲取鎖A是獲取鎖C的必要條件)。因爲線程1已經擁有了鎖A,

所以線程2和3需要一直等到鎖A被釋放。然後在它們嘗試對B或C加鎖之前,必須成功地對A加了鎖。

2)加鎖時限 :

另外一個可以避免死鎖的方法是在嘗試獲取鎖的時候加一個超時時間,這也就意味着在嘗試獲取鎖的過程中若超過了這個時限該線程則放棄對該鎖請求。

並會進行回退並釋放所有已經獲得的鎖,然後等待一段隨機的時間再重試。這段隨機的等待時間讓其它線程有機會嘗試獲取相同的這些鎖,

並且讓該應用在沒有獲得鎖的時候可以繼續運行(加鎖超時後可以先繼續運行乾點其它事情,再回頭來重複之前加鎖的邏輯)。

3)死鎖檢測:

每當一個線程獲得了鎖,會在線程和鎖相關的數據結構中(map、graph等等)將其記下。除此之外,每當有線程請求鎖,也需要記錄在這個數據結構中。

當一個線程請求鎖失敗時,這個線程可以遍歷鎖的關係圖看看是否有死鎖發生。如果檢測到死鎖,就釋放所有鎖,回退,並且等待一段隨機的時間後再重試

遇到死鎖怎麼辦?

我們先了解下死鎖定理: 
                     ①如果資源分配圖中沒有環路,則系統沒有死鎖; 
                     ②如果資源分配圖中出現了環路,則系統可能有死鎖。

從上面的死鎖定理中我們可以知道只要打破死鎖的環路就可以解開死鎖,以下是處理死鎖的兩種名方法:

1)搶佔資源:掛起某些死鎖進程,並搶佔它的資源,將這些資源分配給其他的死鎖進程。但應防止被掛起的進程長時間得不到資源,而處於資源匱乏的狀態。

2)終止(或撤銷)進程:終止或撤銷系統中的一個或多個死鎖進程,直至打破死鎖狀態

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章