銀行兩操作員同時操作同一賬戶就是典型的例子。
比如A、B操作員同時讀取一余額為1000元的賬戶,A操作員為該賬戶增加100元,B操作員同時為該賬戶扣除50元,A先提交,B后提交。最后實際賬戶余額為1000-50=950元,但本該為1000+100-50=1050。這就是典型的並發問題。
樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基於數據版本(Version)記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。
讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大於數據庫表當前版本號,則予以更新,否則認為是過期數據。
對於上面修改用戶帳戶信息的例子而言,假設數據庫中帳戶信息表中有一個version字段,當前值為1;而當前帳戶余額字段(balance)為1000元。假設操作員A先更新完,操作員B后更新。
a、操作員A此時將其讀出(version=1),並從其帳戶余額中增加100(1000+100=1100)。
b、在操作員A操作的過程中,操作員B也讀入此用戶信息(version=1),並從其帳戶余額中扣除50(1000-50=950)。
c、操作員A完成了修改工作,將數據版本號加一(version=2),連同帳戶增加后余額(balance=1100),提交至數據庫更新,此時由於提交數據版本大於數據庫記錄當前版本,數據被更新,數據庫記錄version更新為2。
d、操作員B完成了操作,也將版本號加一(version=2)試圖向數據庫提交數據(balance=950),但此時比對數據庫記錄版本時發現,操作員B提交的數據版本號為2,數據庫記錄當前版本也為2,不滿足 “提交版本必須大於記錄當前版本才能執行更新 “的樂觀鎖策略,因此,操作員B的提交被駁回。
這樣,就避免了操作員B用基於version=1的舊數據修改的結果覆蓋操作員A的操作結果的可能。
操作員A操作如下:
select id, balance, version from account where id="1"; 查詢結果:id=1, balance=1000, version=1 update account set balance=balance+100, version=version+1 where id="1" and version=1 select id, balance, version from account where id="1"; 查詢結果:id=1, balance=1100, version=2
操作員B操作如下:
select id, balance, version from account where id="1"; 查詢結果:id=1, balance=1000, version=1 #操作員A已修改成功,實際account.balance=1100、account.version=2,操作員B也將版本號加一(version=2)試圖向數據庫提交數據(balance=950),但此時比對數據庫記錄版本時發現,操作員B提交的數據版本號為2,數據庫記錄當前版本也為2,不滿足 “提交版本必須大於記錄當前版本才能執行更新 “的樂觀鎖策略,因此,操作員B的提交被駁回。 update account set balance=balance-50, version=version+1 where id="1" and version=1 select id, balance, version from account where id="1"; 查詢結果:id=1, balance=1100, version=2
Hibernate、JPA等ORM框架或者實現,是使用版本號,再判斷UPDATE后返回的數值