樂觀鎖與悲觀鎖

2021-06-25 17:43:15 字數 3148 閱讀 6808

悲觀鎖【pessimistic locking】

顧名思義就是採用一種悲觀的態度來對待事務併發問題,我們認為系統中的併發更新會非常頻繁,並且事務失敗了以後重來的開銷很大,這樣以來,我們就需要採用真正意義上的鎖來進行實現。悲觀鎖的基本思想就是每次乙個事務讀取某一條記錄後,就會把這條記錄鎖住,這樣

其它的事務要想更新,必須等以前的事務提交或者回滾解除鎖。

假如我們資料庫事務的隔離級別設定為讀取已提交或者更低,那麼通過悲觀鎖,我們控制了不可重複讀的問題,但是不能避免幻影讀的問題,因為要想避免我們就需要設定資料庫隔離級別為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進行悲觀控制的缺點。

hibernate:hibernate中支援悲觀鎖的兩種模式lockmode.upgrade【利用for update語句】和lockmode.upgrade_no_wait 【oracle 的特定實現,利用 oracle 的 forupdate nowait 子句實現加鎖。 】

加鎖一般通過以下方法實現: 

criteria.setlockmode

query.setlockmode

session.lock

注意,只有在查詢開始之前(也就是 hiberate 生成 sql 之前)設定加鎖,才會 真正通過資料庫的鎖機制進行加鎖處理,否則,資料已經通過不包含 for update子句的 select sql 載入進來,所謂資料庫加鎖也就無從談起。

樂觀鎖【optimistic locking】

樂觀鎖是在同乙個資料庫事務中我們常採取的策略,因為它能使得我們的系統保持高的效能的情況下,提高很好的併發訪問控制。樂觀鎖,顧名思義就是保持一種樂觀的態度,我們認為系統中的事務併發更新不會很頻繁,即使衝突了也沒事,大不了重新再來一次。它的基本思想就是每次提交乙個事務更新時,我們想看看要修改的東西從上次讀取以後有沒有被其它事務修改過,如果修改過,那麼更新就會失敗。(因此能夠解決第二類丟失修改問題)

因為樂觀鎖其實並不會鎖定任何記錄,所以如果我們資料庫的事務隔離級別設定為讀取已提交或者更低的隔離界別,那麼是不能避免不可重複讀問題的(因為此時讀事務不會阻塞其它事務),所以採用樂觀鎖的時候,系統應該要容許不可重複讀問題的出現。

需要注意的是,樂觀鎖機制往往基於系統中的資料儲存邏輯,因此也具備一定的侷限性,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的使用者更新操作不受我們系統的控制,因此可能會造成髒資料被更新到資料庫中。在 系統設計階段,我們應該充分考慮到這些情況出現的可能性,並進行相應調整(如將樂觀鎖策略在資料庫儲存過程中實現,對外只開放基於此儲存過程的資料更新途徑,而不是將資料庫表直接對外公開)。

實現方式:

大多是基於資料版本 ( 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)

........

在使用jdbc api的情況下,我們需要在每個update語句中,都要進行版本欄位的更新以及判斷,因此如果稍不小心就會出現版本字段沒有更新的問題,相反當前的 orm框架卻為我們做好了一切,我們僅僅需要做的就是在每個實體中都增加version或者是date欄位。

hibernate中使用樂觀鎖:如果我們採用hibernate做為持久層的框架,那麼實現樂觀鎖將變得非常容易,因為框架會幫我們生成相應的sql語句,不僅減少了開發人員的負擔,而且不容易出錯。下面同樣採用version欄位的方式來總結一下:

同樣假如系統中有乙個account的實體類,我們在account中多加乙個version欄位,

public class account

這樣以來每次我們提交事務時,hibernate內部會生成相應的sql語句將版本欄位加1,並且進行相應的版本檢測,如果檢測到併發樂觀鎖定異常,那麼就丟擲staleobjectstateexception.

附xml檔案進行配置 ……

悲觀鎖與樂觀鎖

悲觀鎖與樂觀鎖 悲觀鎖 pessimistic locking 顧名思義就是採用一種悲觀的態度來對待事務併發問題,我們認為系統中的併發更新會非常頻繁,並且事務失敗 了以後重來的開銷很大,這樣以來,我們就需要採用真正意義上的鎖來進行實現。悲觀鎖的基本思想就是每次一 個事務讀取某一條記錄後,就會把這條記...

樂觀鎖與悲觀鎖

鎖 locking 這個概念在我們學習多執行緒的時候曾經接觸過,其實這裡的鎖和多執行緒裡面處理併發的鎖是乙個道理,都是暴力的把資源歸為自己所有。這裡我們用到鎖的目的就是通過一些機制來保證一些資料在某個操作過程中不會被外界修改,這樣的機制,在這裡,也就是所謂的 鎖 即給我們選定的目標資料上鎖,使其無法...

樂觀鎖與悲觀鎖

悲觀鎖 pessimistic lock 顧名思義,就是很悲觀,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會block直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。樂觀鎖 optim...