數據庫鎖

數據庫中的一個非常重要的概念,當多個用戶同時對數據庫併發操作時,會帶來數據不一致的問題,所以,鎖主要用於多用戶環境下保證數據庫完整性和一致性

數據庫鎖作用:處理併發帶來的問題

併發控制的主要採用的技術手段:樂觀鎖、悲觀鎖和時間戳

鎖的分類

數據庫系統角度:排他鎖、共享鎖、更新鎖

程序員角度:悲觀鎖、樂觀鎖

悲觀鎖(Pessimistic Lock)

它很悲觀,每次去拿數據的時候都認爲別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人拿這個數據就會block(阻塞),直到它結束

悲觀鎖,具有強烈的獨佔和排他特性。它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處於鎖定狀態

悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)

傳統的關係數據庫裏用到了很多這種鎖機制,比如行鎖、表鎖、讀鎖、寫鎖等,都是在操作之前先上鎖。

悲觀鎖按使用性質劃分:共享鎖(Share Lock)、排他鎖(Exclusive Lock)、更新鎖

共享鎖(Share Lock)

S鎖,也叫讀鎖,用於所有的只讀數據操作。共享鎖是非獨佔的,允許多個併發事務讀取其鎖定的資源。 

特點:
      1. 多個事務可封鎖同一個共享頁
      2. 任何事務都不能修改該頁
      3. 通常是該頁被讀取完畢,S鎖立即被釋放

在SQL Server中,默認情況下,數據被讀取後,立即釋放共享鎖 
       “SELECT * FROM my_table”:首先鎖定第一頁,讀取之後,釋放對第一頁的鎖定,然後鎖定第二頁。這樣,就允許在讀操作過程中,修改未被鎖定的第一頁。 
       “SELECT * FROM my_table HOLDLOCK”:就要求在整個查詢過程中,保持對錶的鎖定,直到查詢完成才釋放鎖定。

排他鎖(Exclusive Lock)

X鎖,也叫寫鎖,表示對數據進行寫操作。如果一個事務對對象加了排他鎖,其他事務就不能再給它加任何鎖了。
特點:
       1. 僅允許一個事務封鎖此頁 
       2. 其他任何事務必須等到X鎖被釋放才能對該頁進行訪問 
       3. X鎖一直到事務結束才能被釋放

產生排他鎖的SQL語句如下:select * from ad_plan for update;

更新鎖

U鎖,在修改操作的初始化階段用來鎖定可能要被修改的資源,這樣可以避免使用共享鎖造成的死鎖現象

因爲當使用共享鎖時,修改數據的操作分爲兩步: 
       1. 首先獲得一個共享鎖,讀取數據, 
       2. 然後將共享鎖升級爲排他鎖,再執行修改操作。 
       這樣如果有兩個或多個事務同時對一個事務申請了共享鎖,在修改數據時,這些事務都要將共享鎖升級爲排他鎖。這時,這些事務都不會釋放共享鎖,而是一直等待對方釋放,這樣就造成了死鎖。 如果一個數據在修改前直接申請更新鎖,在數據修改時再升級爲排他鎖,就可以避免死鎖。

特點:
       1. 用來預定要對此頁施加X鎖,它允許其他事務讀,但不允許再施加U鎖或X鎖; 
        2. 當被讀取的頁要被更新時,則升級爲X鎖; 
        3. U鎖一直到事務結束時才能被釋放。

悲觀鎖按作用範圍劃分爲:行鎖、表鎖

行鎖:行級別

表鎖:整張表

數據庫能夠確定哪些行需要鎖的情況下使用行鎖,如果不知道會影響哪些行的時候就會使用表鎖

如:一個用戶表user,有主鍵id和用戶生日birthday
       update … where id=?:行鎖
       update … where birthday=?:表鎖
 

樂觀鎖(Optimistic Lock)

它很樂觀,每次去拿數據的時候都認爲別人不會修改,所以,不會上鎖。但是在更新的時候會判斷一下在此期間別人有沒有更新這個數據,可以使用版本號等機制

       樂觀鎖( Optimistic Locking ): 相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖機制。 悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。而樂觀鎖機制在一定程度上解決了這個問題。 

       樂觀鎖,大多是基於數據版本( Version )記錄機制實現。

       樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖

樂觀鎖的實現方式:

1、版本號(version)

       給數據增加一個版本標識,在數據庫上就是表中增加一個version字段,每次更新把這個字段加1,讀取數據的時候把version讀出來,更新的時候比較version,如果還是開始讀取的version就可以更新了,如果現在的version比老的version大,說明有其他事務更新了該數據,並增加了版本號,這時候得到一個無法更新的通知,用戶自行根據這個通知來決定怎麼處理,比如重新開始一遍。這裏的關鍵是判斷version和更新兩個動作需要作爲一個原子單元執行,否則在你判斷可以更新以後正式更新之前有別的事務修改了version,這個時候你再去更新就可能會覆蓋前一個事務做的更新,造成第二類丟失更新,所以你可以使用update … where … and version=”old version”這樣的語句,根據返回結果是0還是非0來得到通知,如果是0說明更新沒有成功,因爲version被改了,如果返回非0說明更新成功

2、時間戳

和版本號基本一樣,只是通過時間戳來判斷而已,注意時間戳要使用數據庫服務器的時間戳不能是業務系統的時間。

3、待更新字段

和版本號方式相似,只是不增加額外字段,直接使用有效數據字段做版本控制信息,因爲有時候我們可能無法改變舊系統的數據庫表結構。假設有個待更新字段叫count,先去讀取這個count,更新的時候去比較數據庫中count的值是不是我期望的值(即開始讀的值),如果是就把我修改的count的值更新到該字段,否則更新失敗。java的基本類型的原子類型對象如AtomicInteger就是這種思想

4、所有字段 

和待更新字段類似,只是使用所有字段做版本控制信息,只有所有字段都沒變化纔會執行更新

樂觀鎖多種實現方式的選擇:

新系統設計可以使用version方式和timestamp方式,需要增加字段,應用範圍是整條數據,不論那個字段修改都會更新version,也就是說兩個事務更新同一條記錄的兩個不相關字段也是互斥的,不能同步進行。

舊系統不能修改數據庫表結構的時候使用數據字段作爲版本控制信息,不需要新增字段,待更新字段方式只要其他事務修改的字段和當前事務修改的字段沒有重疊就可以同步進行,併發性更高

併發控制會造成兩種鎖

  • 活鎖
  • 死鎖

活鎖 

指的是T1封鎖了數據R,T2同時也請求封鎖數據R,T3也請求封鎖數據R,當T1釋放了鎖之後,T3會鎖住R,T4也請求封鎖R,則T2就會一直等待下去。 

解決方法:採用“先來先服務”策略可以避免。

死鎖 

就是我等你,你又等我,雙方就會一直等待下去。比如:T1封鎖了數據R1,正請求對R2封鎖,而T2封住了R2,正請求封鎖R1,這樣就會導致死鎖,死鎖這種沒有完全解決的方法,只能儘量預防

死鎖的四個必要條件

互斥條件(Mutual exclusion):資源不能被共享,只能由一個進程使用。
       請求與保持條件(Hold and wait):已經得到資源的進程可以再次申請新的資源。
       非剝奪條件(No pre-emption):已經分配的資源不能從相應的進程中被強制地剝奪。
       循環等待條件(Circular wait):系統中若干進程組成環路,該環路中每個進程都在等待相鄰進程正佔用的資源。

處理死鎖的策略

      1.忽略該問題。例如鴕鳥算法,該算法可以應用在極少發生死鎖的的情況下。爲什麼叫鴕鳥算法呢,因爲傳說中鴕鳥看到危險就把頭埋在地底下,可能鴕鳥覺得看不到危險也就沒危險了吧
      2.檢測死鎖並且恢復。
      3.仔細地對資源進行動態分配,以避免死鎖。
      4.通過破除死鎖四個必要條件之一,來防止死鎖產生。

系統判定死鎖的方法

       超時法:如果某個事物的等待時間超過指定時限,則判定爲出現死鎖;
       等待圖法:如果事務等待圖中出現了迴路,則判斷出現了死鎖。

       對於解決死鎖的方法,只能是撤銷一個處理死鎖代價最小的事務,釋放此事務持有的所有鎖,同時對撤銷的事務所執行的數據修改操作必須加以恢復

 

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