樂觀鎖和悲觀鎖策略的區別與實現

bfc99發表於2015-07-17
樂觀鎖和悲觀鎖策略的區別與實現

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 儲戶帳號=儲戶甲的銀行帳號;

那麼,即使是在樂觀鎖策略的情況下,依然可以保證資料的正確性和完整性。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/22207394/viewspace-1736342/,如需轉載,請註明出處,否則將追究法律責任。

相關文章