版本號機制
一般是在數據表中加上版本號字段 version
,表示數據被修改的次數。當數據被修改時,這個字段值會加1。
舉個簡單的例子:假設帳戶信息表中有一個 version 字段,當前值為 1 ,而當前帳戶的余額( balance )為 100 。
- 操作員 A 此時准備將其讀出( version=1 ),並從其帳戶余額中扣除 50( 100-50 );
- 操作員 A 操作的過程中,操作員 B 也讀入此用戶信息( version=1 ),並從其帳戶余額中扣除 20 ( 100-20 );
- 操作員 A 完成修改工作,將數據版本號加1( version=2 ),連同帳戶扣除后余額( balance=50 ),提交到數據庫完成更新;
- 操作員 B 完成了操作,也將版本號加1( version=2 )試圖向數據庫提交數據( balance=80 ),但此時比對數據庫記錄版本發現,操作員 B 提交的數據版本號為 2 ,數據庫記錄的當前版本也為 2 ,不滿足 “提交版本必須大於記錄當前版本才能執行更新“ 的樂觀鎖策略。
因此,操作員 B 的提交被駁回。這樣,就避免了操作員 B 用基於 version=1 的舊數據修改,最終造成覆蓋操作員 A 操作結果的可能。
CAS 算法
即 compare and swap(比較與交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖(沒有線程被阻塞)的情況下實現多線程之間的變量同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS 算法涉及到三個操作數:
- 需要讀寫的內存值 V
- 進行比較的值 A
- 擬寫入的新值 B
當且僅當 V 的值等於 A 時,CAS 通過原子方式用新值 B 來更新 V 的值,否則不會執行任何操作(比較和替換是一個 native 原子操作)。一般情況下,這是一個自旋操作,即不斷的重試。
上面介紹了2種辦法,下面說下項目中實操
文件標注結果保存,多人或多場景可以修改,並發時可能出現問題
當修改值時,使用樂觀鎖 + findAndModify的原子操作
public Result updateResultInfo(Result result, JSONObject finalResult) { Query query = new Query(); query.addCriteria(Criteria.where("userTaskId").is(result.getUserTaskId()) .and("userTaskFileId").is(result.getUserTaskFileId()) .and("isDeleted").is(0) .and("version").is(result.getVersion())); Update update = new Update(); update.set("finalResult", finalResult); update.set("version", result.getVersion() + 1); update.set("stamp", result.getStamp()); return resultMongoTemplate.findAndModify(query, update, Result.class); }
當新增數據時,可能插入數據時,已經被別人插入了。此時使用分布式鎖,redis鎖或zk鎖等都可以