在解決高并發問題時,如果是分布式系統顯然我們隻能夠使用資料庫端加鎖機制來解決這個問題,但是這種同步機制或者資料庫實體鎖機制會犧牲一部分的性能,是以常常以另外一種方式來解決這個問題 就是樂觀鎖模式
銀行兩操作員同時操作同一賬戶就是典型的樂觀鎖模式。
比如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後傳回的數值