樂觀鎖和悲觀鎖,就是對數據庫進行操作時使用的,樂觀鎖是update是開始,悲觀鎖是查詢記錄那一刻開始,兩者結束都是commit或者 rollback
悲觀鎖,一直鎖,不讓改 樂觀鎖,只在更新的時候判斷一下別人有沒有改過這個數據,保證商品只被賣出一次,可以使用版本號等機制,可以提高數據吞吐量
並發控制機制,當一個用戶鎖住了數據之后,其他用戶就不能訪問
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖。
兩種鎖各有優缺點,不可認為一種好於另一種,像樂觀鎖適用於寫比較少的情況下,即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果經常產生沖突,上層應用會不斷的進行retry,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。
1、無論是選擇悲觀鎖策略,還是樂觀鎖策略。如果一個對象被上了鎖,那么該對象都會受這個鎖的控制和影響。如果這個鎖是個排它鎖,那么其它會話都不能修改它。
2、選擇悲觀鎖策略,還是樂觀鎖策略,這主要是由應用和業務需求來確定的。如果你的應用和業務經常會出現從我看到要修改的記錄的值,到我修改完成該記錄這個時間段內,該記錄有較大概率被其它會話所修改。換句話說就是,在我真正去做出修改時,這個記錄的值很可能已經與我當初看到的不同了。那么這時,采取悲觀鎖策略,也許是更好的。而采取悲觀鎖策略的一個典型操作就是 select ... for undate。通過這種操作,使得從我一開始查看該記錄起,這條記錄就被上了排它鎖,不允許其它會話再對該記錄有任何修改。
相對的,如果你的業務和應用基本上很少出現這種情景,那么選擇樂觀鎖策略也許就會更好。
3、這兩種策略的核心其實就是持有鎖的時間的起止點不同,悲觀鎖是從讀取記錄的那一刻就開始了,而樂觀鎖只從UPDATE那一刻開始;結束的點兩者是一樣的,都是發出commit或rollback命令。所以,悲觀鎖策略會使鎖的持續時間更長,而樂觀鎖的持續時間則較短。其影響就是並發。悲觀鎖的並發性低於樂觀鎖。
4、無論是采用哪種策略,都要保證數據的完整性。所以,在采用樂觀鎖策略時,是有可能出現數據的不完整。舉例來說:儲戶甲的存款余額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;)
該記錄就已經被鎖定了,這時甲網上消費的行為導致的交易系統從甲的帳戶中扣減的操作就會處於等待狀態。直至銀行員工提交了相關指令,交易系統才能去扣減甲的錢款。這樣,就可以確保甲的帳戶余額是正確的。
悲觀鎖的策略顯然可以保證業務的正確性和完整性。但再設想一下,如果甲在存款時,銀行員工內急,或者儲戶甲說等一等,我要考慮一下是否再多存一些。那么,銀行員工的操作就不會提交,這時網上交易系統對甲帳戶的扣款操作就會一直處於等待狀態,或者在等待一定時間后,返回一個扣款失敗的提示。這對於系統的效率和客戶來說,都不是一個好的體驗。
5、因為考慮到悲觀鎖策略可以產生的這種問題,所以,我們在設計應用時,可以采取一些其它方法來避免上述情況的發生。其思想就是在真正提交時,確認要修改的數據沒有變化過。主要的方法如下:
(1)、更新時帶入原始的數據。
update 存款余額表
set 余額=150
where 儲戶帳號=儲戶甲的銀行帳號 and 余額=100;
(2)、在記錄上增加修改的時間戳(也可利用ora_rowscn偽列)。即在事務開始時,獲取該記錄的時間戳,修改時,校驗該時間戳,若一致則修改。
6、其實,我上面舉的這個例子,如果在業務設計時,選擇更新指令為
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)選項,在查詢記錄的時候我們就對記錄加上了更新鎖,表示我們即將對此記錄進行更新. 注意更新鎖和共享鎖是不沖突的,也就是其他用戶還可以查詢此表的內容,但是和更新鎖和排它鎖是沖突的.所以其他的更新用戶就會阻塞.
結論
在實際生產環境里邊,如果並發量不大且不允許臟讀,可以使用悲觀鎖解決並發問題;但如果系統的並發非常大的話,悲觀鎖定會帶來非常大的性能問題,所以我們就要選擇樂觀鎖定的方法.
https://www.cnblogs.com/jinlinFighting/p/5933724.html
