樂觀鎖與悲觀鎖
在多用戶環境中,在同一時間可能會有多個用戶更新相同的記錄,這會產生沖突。這就是著名的並發性問題。
典型的沖突有:
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 將在整個表上置排它鎖直至該命令或事務結束。這將防止其他進程讀取或修改表中的數據。
找了個很好的例子,引用一下https://yq.aliyun.com/articles/6899
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 [email protected]
commit
我們在查詢的時候使用了with (UPDLOCK)選項,在查詢記錄的時候我們就對記錄加上了更新鎖,表示我們即將對此記錄進行更新. 註意更新鎖和共享鎖是不沖突的,也就是其他用戶還可以查詢此表的內容,但是和更新鎖和排它鎖是沖突的.所以其他的更新用戶就會阻塞.
結論
在實際生產環境裏邊,如果並發量不大且不允許臟讀,可以使用悲觀鎖解決並發問題;但如果系統的並發非常大的話,悲觀鎖定會帶來非常大的性能問題,所以我們就要選擇樂觀鎖定的方法.
樂觀鎖與悲觀鎖