悲觀鎖
- 對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度
- 在整個數據處理過程中,將數據處於鎖定狀態
- 悲觀鎖的實現往往依靠數據庫提供的鎖機制
樂觀鎖
- 大多是基於數據版本記錄機制實現
- 數據版本即為數據增加一個版本標識,在基於數據庫的版本解決方案中,一般是通過為數據庫增加一個“version”字段來實現
- 讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一
- 將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本大於數據庫表當前版本號,則予以更新,否則認為是過期數據
- https://wenku.baidu.com/view/b28924a27d1cfad6195f312b3169a4517723e5e7.html
-
為什么需要鎖(並發控制)?
在多用戶環境中,在同一時間可能會有多個用戶更新相同的記錄,這會產生沖突。這就是著名的並發性問題。
典型的沖突有:
l 丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:用戶A把值從6改為2,用戶B把值從2改為6,則用戶A丟失了他的更新。
l 臟讀:當一個事務讀取其它完成一半事務的記錄時,就會發生臟讀取。例如:用戶A,B看到的值都是6,用戶B把值改為2,用戶A讀到的值仍為6。
為了解決這些並發帶來的問題。 我們需要引入並發控制機制。
悲觀鎖
顧名思義就是采用一種悲觀的態度來對待事務並發問題,我們認為系統中的並發更新會非常頻繁,並且事務失敗了以后重來的開銷很大,這樣以來,我們就需要采用真正意義上的鎖來進行實現。悲觀鎖的基本思想就是每次一個事務讀取某一條記錄后,就會把這條記錄鎖住,這樣
其它的事務要想更新,必須等以前的事務提交或者回滾解除鎖。假如我們數據庫事務的隔離級別設置為讀取已提交或者更低,那么通過悲觀鎖,我們控制了不可重復讀的問題,但是不能避免幻影讀的問題,因為要想避免我們就需要設置數據庫隔離級別為Serializable,而一般情況下我們都會采取讀取已提交或者更低隔離級別,並配合樂觀或者悲觀鎖來實現並發控制,所以幻影讀問題是不能避免的,如果想避免幻影讀問題,那么你只能依靠數據庫的serializable隔離級別(幸運的是幻影讀問題一般情況下不嚴重)
悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能 真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。
實現方式:
JDBC方式:在JDBC中使用悲觀鎖,需要使用select for update語句,假如我們系統中有一個Account的類,我們可以采用如下的方式來進行:
Select * from Account where ...(where condition).. for update.
當使用了for update語句后,每次在讀取或者加載一條記錄的時候,都會鎖住被加載的記錄,那么當其他事務如果要更新或者是加載此條記錄就會因為不能獲得鎖而阻塞,這樣就避免了不可重復讀以及臟讀的問題,但是其他事務還是可以插入和刪除記錄,這樣也許同一個事務中的兩次讀取會得到不同的結果集,但是這不是悲觀鎖鎖造成的問題,這是我們數據庫隔離級別所造成的問題。
最后還需要注意的一點就是每個沖突的事務中,我們必須使用select for update 語句來進行數據庫的訪問,如果一些事務沒有使用select for update語句,那么就會很容易造成錯誤,這也是采用JDBC進行悲觀控制的缺點。
樂觀鎖
樂觀鎖是在同一個數據庫事務中我們常采取的策略,因為它能使得我們的系統保持高的性能的情況下,提高很好的並發訪問控制。樂觀鎖,顧名思義就是保持一種樂觀的態度,我們認為系統中的事務並發更新不會很頻繁,即使沖突了也沒事,大不了重新再來一次。它的基本思想就是每次提交一個事務更新時,我們想看看要修改的東西從上次讀取以后有沒有被其它事務修改過,如果修改過,那么更新就會失敗。(因此能夠解決第二類丟失修改問題)
因為樂觀鎖其實並不會鎖定任何記錄,所以如果我們數據庫的事務隔離級別設置為讀取已提交或者更低的隔離界別,那么是不能避免不可重復讀問題的(因為此時讀事務不會阻塞其它事務),所以采用樂觀鎖的時候,系統應該要容許不可重復讀問題的出現。
需要注意的是,樂觀鎖機制往往基於系統中的數據存儲邏輯,因此也具備一定的局限性,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的用戶更新操作不受我們系統的控制,因此可能會造成臟數據被更新到數據庫中。在 系統設計階段,我們應該充分考慮到這些情況出現的可能性,並進行相應調整(如將樂觀鎖策略在數據庫存儲過程中實現,對外只開放基於此存儲過程的數據更新途徑,而不是將數據庫表直接對外公開)。
實現方式:
大多是基於數據版本 ( Version )記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。
讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提 交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據 版本號大於數據庫表當前版本號,則予以更新,否則認為是過期數據。假如系統中有一個Account的實體類,我們在Account中多加一個version字段,那么我們JDBC Sql語句將如下寫:
Select a.version....from Account as a where (where condition..)
Update Account set version = version+1.....(another field) where version =?...(another contidition)這樣以來我們就可以通過更新結果的行數來進行判斷,如果更新結果的行數為0,那么說明實體從加載以來已經被其它事務更改了,所以就拋出自定義的樂觀鎖定異常(或者也可以采用Spring封裝的異常體系)。具體實例如下:
.......
int rowsUpdated = statement.executeUpdate(sql);
If(rowsUpdated= =0){
throws new OptimisticLockingFailureException();
}
........在使用JDBC API的情況下,我們需要在每個update語句中,都要進行版本字段的更新以及判斷,因此如果稍不小心就會出現版本字段沒有更新的問題,相反當前的 ORM框架卻為我們做好了一切,我們僅僅需要做的就是在每個實體中都增加version或者是Date字段。
那么究竟該選用什么類型的鎖?
這里是沒有確切的答案的,因為使用什么樣的鎖該由具體情況決定。你需要根據你應用程序的需求選擇相應類型的鎖。
除非你認為一個文檔會存在重度的競爭,樂觀鎖的開銷要遠低於悲觀鎖—— 奪取你需要的,迅速做出修改並保存。如果被系統中其他人搶奪,你只能繼續嘗試直到成功。
使用了悲觀鎖,你可以對一個指定的項目進行互斥存取 —— 當它被鎖定時,其它的線程都不可以訪問。這里的關鍵在於悲觀鎖在操作失敗后必須得到釋放。想象一下:你已經獲得了對象A,但獲得對象B之前你不想放棄對象A;但是另一個已經獲得了對象B的用戶,在獲得對象A之前也不想放棄對象B。那么這時候就需要用到超時設置,它可以打破死鎖和處理釋放鎖失敗的情況。
簡而起見,用戶通常會在整個應用程序中使用相同的鎖策略。如果在整個應用程序中都有着同樣的需求,這么做也無可非議。事實上,情況不是這樣的 —— 不同的應用程序對象有着不同的訪問需求。
最終的見解
當然不是在所有的情況下,樂觀鎖都是最好的解決方案。在許多用例中樂觀鎖可能會有很好的表現,但在其它情況下可能就會需要像悲觀鎖這樣更嚴格的方案。
當然鎖也不是適合所有的情況 —— 如果出現鎖競爭,你的應用程序可能就會拋錨。如果一個線程已經獲得了一個鎖,而OS又沒有對這個鎖的安排;那么其他想爭用這個鎖的線程將會被阻塞。當然其中的一個選擇就是避免完全加鎖,盡量的使用原子操作。這些API對存在高度競爭的數據是很有效的。
-
只寫鎖
:
Lock tables
表名
write;
入下圖實驗
A
用戶設置只寫模式
自己可以讀也可以寫
但是別的用戶不能寫也不
能讀
解鎖兩種辦法
自己用戶直接退出或者執行
unlocak tables;
悲觀鎖
:
悲觀鎖就是程序自帶的鎖
比如
update
delete
insert
這些
執行的時候會默認加個鎖
它指的是對數據被外界
(包括本系統當前的其他事務,
以及來自外部系統的事務處理)
修改
持保守態度,因此,在整個數據處理過程中,將數據處於鎖定狀態。悲觀鎖的實現,往往依
靠
數據庫
提供的鎖機制
(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,
否
則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)
。
說白了他就
是排他鎖
!
樂觀鎖
:
樂觀鎖
相對悲觀鎖而言,
樂觀鎖假設認為數據一般情況下不會造成沖突,
所以在數據
進行提交更新的時候,
才會正式對數據的沖突與否進行檢測,
如果發現沖突了,
則讓返回用
戶錯誤的信息,
讓用戶決定如何去做。
那么我們如何實現樂觀鎖呢,
一般來說有以下
2
種方
式:
1:
使用數據版本(
Version
)記錄機制實現,這是樂觀鎖最常用的一種實現方式。什么
是數據版本?
就是給數據增加一個版本標識,一般是通過為數據庫表增加一個數字類型的
“
version
”
字段來實現。
當讀取數據時,
將
version
字段的值一同讀出,
數據每更新一次,對
此
version
值加一。當我們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一
次取出來的
version
值進行比對,如果數據庫表當前版本號與第一次取出來的
version
值相
等,想等就讓他更新修改,否則認為是過期數據。
2:
樂觀鎖定的第二種實現方式和第一種差不多,同樣是在需要樂觀鎖控制的
table
中增
加一個字段,名稱無所謂,字段類型使用時間戳(
timestamp
)
,
和上面的
version
類似,也
是在更新提交的時候檢查當前數據庫中數據的時間戳和自己更新前取到的時間戳進行對比,
如果一致則
OK
,否則就是版本沖突