總結樂觀鎖和悲觀鎖

樂觀鎖和悲觀鎖,就是對數據庫進行操做時使用的,樂觀鎖是update是開始,悲觀鎖是查詢記錄那一刻開始,二者結束都是commit或者 rollback數據庫

悲觀鎖,一直鎖,不讓改 樂觀鎖,只在更新的時候判斷一下別人有沒有改過這個數據,保證商品只被賣出一次,可使用版本號等機制,能夠提升數據吞吐量
併發

 併發控制機制,當一個用戶鎖住了數據以後,其餘用戶就不能訪問性能

 

悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。spa


樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量,像數據庫若是提供相似於write_condition機制的其實都是提供的樂觀鎖。.net


兩種鎖各有優缺點,不可認爲一種好於另外一種,像樂觀鎖適用於寫比較少的狀況下,即衝突真的不多發生的時候,這樣能夠省去了鎖的開銷,加大了系統的整個吞吐量。但若是常常產生衝突,上層應用會不斷的進行retry,這樣反卻是下降了性能,因此這種狀況下用悲觀鎖就比較合適。設計

 

一、不管是選擇悲觀鎖策略,仍是樂觀鎖策略。若是一個對象被上了鎖,那麼該對象都會受這個鎖的控制和影響。若是這個鎖是個排它鎖,那麼其它會話都不能修改它。 對象

 

二、選擇悲觀鎖策略,仍是樂觀鎖策略,這主要是由應用和業務需求來肯定的。若是你的應用和業務常常會出現從我看到要修改的記錄的值,到我修改完成該記錄這個時間段內,該記錄有較大機率被其它會話所修改。換句話說就是,在我真正去作出修改時,這個記錄的值極可能已經與我當初看到的不一樣了。那麼這時,採起悲觀鎖策略,也許是更好的。而採起悲觀鎖策略的一個典型操做就是 select ... for undate。經過這種操做,使得從我一開始查看該記錄起,這條記錄就被上了排它鎖,不容許其它會話再對該記錄有任何修改。索引

 

相對的,若是你的業務和應用基本上不多出現這種情景,那麼選擇樂觀鎖策略也許就會更好。進程

 

三、這兩種策略的核心其實就是持有鎖的時間的起止點不一樣,悲觀鎖是從讀取記錄的那一刻就開始了,而樂觀鎖只從UPDATE那一刻開始;結束的點二者是同樣的,都是發出commit或rollback命令。因此,悲觀鎖策略會使鎖的持續時間更長,而樂觀鎖的持續時間則較短。其影響就是併發。悲觀鎖的併發性低於樂觀鎖。事務

 

四、不管是採用哪一種策略,都要保證數據的完整性。因此,在採用樂觀鎖策略時,是有可能出現數據的不完整。舉例來講:儲戶甲的存款餘額100元,假設在幾乎相同的時刻,發生了兩筆業務,業務1爲其存入了50元,另外一個業務是其網上購物消費了30元。顯然,這兩個操做結束後,甲的存款餘額應爲120元(100+50-30)。但咱們設想一下在數據庫層面,可能出現這種狀況,當其在銀行櫃檯存入50元時,銀行操做員收到了甲存入的50元現金,並經過 select 語句看到甲的當前餘額爲100元(其發出的指令是下面的語句:

 

select 餘額

 

   from 存款餘額表

 

where 儲戶賬號=儲戶甲的銀行賬號;)

 

,接着,發出指令

 

update 存款餘額表

 

      set 餘額=150

 

    where 儲戶賬號=儲戶甲的銀行賬號;

 

但就在其看到甲的餘額爲100元,到其修改甲的餘額爲150這期間,甲在網上的消費行爲致使交易系統已經將甲的餘額變成了70元(100-30)並提交了。當銀行員工發出的指令也被提交後,甲的餘額變成了150元,至關於甲網上消費的行爲沒有產生任何扣款。這顯然是不正確的,更是要避免出現的。

 

若是這套系統採用的是悲觀鎖策略,那麼在從銀行員工查看甲當前餘額的那一個時刻起(這時查詢的指令就會是:

 

select 餘額

 

   from 存款餘額表

 

where 儲戶賬號=儲戶甲的銀行賬號 for update;)

 

該記錄就已經被鎖定了,這時甲網上消費的行爲致使的交易系統從甲的賬戶中扣減的操做就會處於等待狀態。直至銀行員工提交了相關指令,交易系統才能去扣減甲的錢款。這樣,就能夠確保甲的賬戶餘額是正確的。

 

悲觀鎖的策略顯然能夠保證業務的正確性和完整性。但再設想一下,若是甲在存款時,銀行員工內急,或者儲戶甲說等一等,我要考慮一下是否再多存一些。那麼,銀行員工的操做就不會提交,這時網上交易系統對甲賬戶的扣款操做就會一直處於等待狀態,或者在等待必定時間後,返回一個扣款失敗的提示。這對於系統的效率和客戶來講,都不是一個好的體驗。

 

五、由於考慮到悲觀鎖策略能夠產生的這種問題,因此,咱們在設計應用時,能夠採起一些其它方法來避免上述狀況的發生。其思想就是在真正提交時,確認要修改的數據沒有變化過。主要的方法以下:

 

(1)、更新時帶入原始的數據。

 

     update 存款餘額表

 

      set 餘額=150

 

    where 儲戶賬號=儲戶甲的銀行賬號 and 餘額=100;

 

(2)、在記錄上增長修改的時間戳(也可利用ora_rowscn僞列)。即在事務開始時,獲取該記錄的時間戳,修改時,校驗該時間戳,若一致則修改。

 

六、其實,我上面舉的這個例子,若是在業務設計時,選擇更新指令爲

 

update 存款餘額表

 

      set 餘額=餘額+50

 

    where 儲戶賬號=儲戶甲的銀行賬號;

 

那麼,即便是在樂觀鎖策略的狀況下,依然能夠保證數據的正確性和完整性。

 

 

 

 

 

 

 

 

 

 

爲何須要鎖(併發控制)?

在多用戶環境中,在同一時間可能會有多個用戶更新相同的記錄,這會產生衝突。這就是著名的併發性問題。

典型的衝突有:

l 丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:用戶A把值從6改成2,用戶B把值從2改成6,則用戶A丟失了他的更新。

l 髒讀:當一個事務讀取其它完成一半事務的記錄時,就會發生髒讀取。例如:用戶A,B看到的值都是6,用戶B把值改成2,用戶A讀到的值仍爲6。

爲了解決這些併發帶來的問題。 咱們須要引入併發控制機制。

併發控制機制

    最經常使用的處理多用戶併發訪問的方法是加鎖。當一個用戶鎖住數據庫中的某個對象時,其餘用戶就不能再訪問該對象。加鎖對併發訪問的影響體如今鎖的粒度上。好比,放在一個表上的鎖限制對整個表的併發訪問;放在數據頁上的鎖限制了對整個數據頁的訪問;放在行上的鎖只限制對該行的併發訪問。可見行鎖粒度最小,併發訪問最好,頁鎖粒度最大,表鎖介於2者之間。

悲觀鎖:假定會發生併發衝突,屏蔽一切可能違反數據完整性的操做。[1]      悲觀鎖假定其餘用戶企圖訪問或者改變你正在訪問、更改的對象的機率是很高的,所以在悲觀鎖的環境中,在你開始改變此對象以前就將該對象鎖住,而且直到你提交了所做的更改以後才釋放鎖。悲觀的缺陷是不管是頁鎖仍是行鎖,加鎖的時間可能會很長,這樣可能會長時間的限制其餘用戶的訪問,也就是說悲觀鎖的併發訪問性很差。

樂觀鎖:假設不會發生併發衝突,只在提交操做時檢查是否違反數據完整性。[1] 樂觀鎖不能解決髒讀的問題。    樂觀鎖則認爲其餘用戶企圖改變你正在更改的對象的機率是很小的,所以樂觀鎖直到你準備提交所做的更改時纔將對象鎖住,當你讀取以及改變該對象時並不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖能夠用較大的鎖粒度得到較好的併發訪問性能。可是若是第二個用戶剛好在第一個用戶提交更改以前讀取了該對象,那麼當他完成了本身的更改進行提交時,數據庫就會發現該對象已經變化了,這樣,第二個用戶不得不從新讀取該對象並做出更改。這說明在樂觀鎖環境中,會增長併發用戶讀取對象的次數。

 

     從數據庫廠商的角度看,使用樂觀的頁鎖是比較好的,尤爲在影響不少行的批量操做中能夠放比較少的鎖,從而下降對資源的需求提升數據庫的性能。再考慮彙集索引。在數據庫中記錄是按照彙集索引的物理順序存放的。若是使用頁鎖,當兩個用戶同時訪問更改位於同一數據頁上的相鄰兩行時,其中一個用戶必須等待另外一個用戶釋放鎖,這會明顯地下降系統的性能。interbase和大多數關係數據庫同樣,採用的是樂觀鎖,並且讀鎖是共享的,寫鎖是排他的。能夠在一個讀鎖上再放置讀鎖,但不能再放置寫鎖;你不能在寫鎖上再放置任何鎖。鎖是目前解決多用戶併發訪問的有效手段。  

樂觀鎖應用

1.      使用自增加的整數表示數據版本號。更新時檢查版本號是否一致,好比數據庫中數據版本爲6,更新提交時version=6+1,使用該version值(=7)與數據庫version+1(=7)做比較,若是相等,則能夠更新,若是不等則有可能其餘程序已更新該記錄,因此返回錯誤。

2.      使用時間戳來實現.

注:對於以上兩種方式,Hibernate自帶實現方式:在使用樂觀鎖的字段前加annotation: @Version, Hibernate在更新時自動校驗該字段。

悲觀鎖應用

須要使用數據庫的鎖機制,好比SQL SERVER 的TABLOCKX(排它表鎖) 此選項被選中時,SQL  Server  將在整個表上置排它鎖直至該命令或事務結束。這將防止其餘進程讀取或修改表中的數據。

SqlServer中使用

Begin Tran
select top 1 @TrainNo=T_NO
         from Train_ticket   with (UPDLOCK)   where S_Flag=0

      update Train_ticket
         set T_Name=user,
             T_Time=getdate(),
             S_Flag=1
         where T_NO=@TrainNo
commit

咱們在查詢的時候使用了with (UPDLOCK)選項,在查詢記錄的時候咱們就對記錄加上了更新鎖,表示咱們即將對此記錄進行更新. 注意更新鎖和共享鎖是不衝突的,也就是其餘用戶還能夠查詢此表的內容,可是和更新鎖和排它鎖是衝突的.因此其餘的更新用戶就會阻塞.

結論

在實際生產環境裏邊,若是併發量不大且不容許髒讀,可使用悲觀鎖解決併發問題;但若是系統的併發很是大的話,悲觀鎖定會帶來很是大的性能問題,因此咱們就要選擇樂觀鎖定的方法.

相關文章
相關標籤/搜索