mybatis和JPA實現樂觀鎖解決併發問題-阿里巴巴JAVA開發手冊詳細解讀
在阿里巴巴近期發出的阿里巴巴JAVA開發手冊(終極版)中有這樣一條記錄。
【強制】併發修改同一記錄時,避免更新丟失,需要加鎖。要麼在應用層加鎖,要麼在快取加鎖,要麼在資料庫層使用樂觀鎖,使用version作為更新依據。
說明:如果每次訪問衝突概率小於20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小於3次。
那麼什麼是樂觀鎖呢?
樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認為資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則讓返回使用者錯誤的資訊,讓使用者決定如何去做。那麼我們如何實現樂觀鎖呢,一般來說有以下2種方式:
通過例項讓大家詳細理解
銀行兩操作員同時操作同一賬戶就是典型的例子。
比如A、B操作員同時讀取一餘額為1000元的賬戶,A操作員為該賬戶增加100元,B操作員同時為該賬戶扣除50元,A先提交,B後提交。最後實際賬戶餘額為1000-50=950元,但本該為1000+100-50=1050。這就是典型的併發問題。
對於上面修改使用者帳戶資訊的例子而言,假設資料庫中帳戶資訊表中有一個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的操作結果的可能。
資料建表
t_goods表,增加一個version欄位,資料預設version值為0。
t_goods表初始資料如下:
Sql程式碼- mysql> select * from t_goods;
- +----+--------+------+---------+
- | id | status | name | version |
- +----+--------+------+---------+
- | 1 | 1 | 道具 | 0 |
- | 2 | 2 | 裝備 | 1 |
- +----+--------+------+---------+
- 2 rowsinset
mybatis實現
Goods實體類:
Java程式碼- /**
- * ClassName: Goods <br/>
- * Function: 商品實體. <br/>
- * date: 2013-5-8 上午09:16:19 <br/>
- * @author [email protected]
- */
- publicclass Goods implements Serializable {
- /**
- * serialVersionUID:序列化ID.
- */
- privatestaticfinallong serialVersionUID = 6803791908148880587L;
- /**
- * id:主鍵id.
- */
- privateint id;
- /**
- * status:商品狀態:1未下單、2已下單.
- */
- privateint status;
- /**
- * name:商品名稱.
- */
- private String name;
- /**
- * version:商品資料版本號.
- */
- privateint version;
- @Override
- public String toString(){
- return"good id:"+id+",goods status:"+status+",goods name:"+name+",goods version:"+version;
- }
- //setter and getter
- }
GoodsDao
Java程式碼- /**
- * updateGoodsUseCAS:使用CAS(Compare and set)更新商品資訊. <br/>
- *
- * @author [email protected]
- * @param goods 商品物件
- * @return 影響的行數
- */
- int updateGoodsUseCAS(Goods goods);
mapper.xml
Xml程式碼- <updateid="updateGoodsUseCAS"parameterType="Goods">
- <![CDATA[
- update t_goods
- set status=#{status},name=#{name},version=version+1
- where id=#{id} and version=#{version}
- ]]>
- </update>
GoodsDaoTest測試類
Java程式碼- @Test
- publicvoid goodsDaoTest(){
- int goodsId = 1;
- //根據相同的id查詢出商品資訊,賦給2個物件
- Goods goods1 = this.goodsDao.getGoodsById(goodsId);
- Goods goods2 = this.goodsDao.getGoodsById(goodsId);
- //列印當前商品資訊
- System.out.println(goods1);
- System.out.println(goods2);
- //更新商品資訊1
- goods1.setStatus(2);//修改status為2
- int updateResult1 = this.goodsDao.updateGoodsUseCAS(goods1);
- System.out.println("修改商品資訊1"+(updateResult1==1?"成功":"失敗"));
- //更新商品資訊2
- goods1.setStatus(2);//修改status為2
- int updateResult2 = this.goodsDao.updateGoodsUseCAS(goods1);
- System.out.println("修改商品資訊2"+(updateResult2==1?"成功":"失敗"));
- }
輸出結果:
Shell程式碼- good id:1,goods status:1,goods name:道具,goods version:1
- good id:1,goods status:1,goods name:道具,goods version:1
- 修改商品資訊1成功
- 修改商品資訊2失敗
說明:
在GoodsDaoTest測試方法中,我們同時查出同一個版本的資料,賦給不同的goods物件,然後先修改good1物件然後執行更新操作,執行成功。然後我們修改goods2,執行更新操作時提示操作失敗。此時t_goods表中資料如下:
Sql程式碼- mysql> select * from t_goods;
- +----+--------+------+---------+
- | id | status | name | version |
- +----+--------+------+---------+
- | 1 | 2 | 道具 | 2 |
- | 2 | 2 | 裝備 | 2 |
- +----+--------+------+---------+
- 2 rowsinset
- mysql>
我們可以看到 id為1的資料version已經在第一次更新時修改為2了。所以我們更新good2時update where條件已經不匹配了,所以更新不會成功,具體sql如下:
Sql程式碼- update t_goods
- set status=2,version=version+1
- where id=#{id} and version=#{version};
這樣我們就在Mybatis實現了樂觀鎖
JPA實現持久層使用jpa時比較簡單,JPA預設提供了一個註解@Version
,我們只需要在剛才的實體類中做相應的註解就可以。
Goods實體類:
Java程式碼- /**
- * ClassName: Goods <br/>
- * Function: 商品實體. <br/>
- * date: 2013-5-8 上午09:16:19 <br/>
- * @author [email protected]
- */
- publicclass Goods implements Serializable {
- /**
- * serialVersionUID:序列化ID.
- */
- privatestaticfinallong serialVersionUID = 6803791908148880587L;
- /**
- * id:主鍵id.
- */
@Id @GenericGenerator(name = "PKUUID", strategy = "uuid2") @GeneratedValue(generator = "PKUUID") @Column(length = 36)
- privateInteger id;
- /**
- * status:商品狀態:1未下單、2已下單.
- */
-
@Column(name = "status")
- private
Integer
status; - /**
- * name:商品名稱.
- */
-
@Column(name = "name")
- private String name;
- /**
- * version:商品資料版本號.
- */
@Version
- privateInteger version;
- @Override
- public String toString(){
- return"good id:"+id+",goods status:"+status+",goods name:"+name+",goods version:"+version;
- }
- //setter and getter
- }
@Version
註解的欄位,我們不需要像mybatis中一樣手動去控制,每一次save操作會在原來的基礎上+1,如果初始為null,則springdata自動設定其為0。總結
樂觀鎖,用在一些敏感業務資料上,而其本身的修飾:樂觀,代表的含義便是相信大多數場景下version是一致的。但是從業務角度出發又要保證資料的嚴格一致性,避免髒讀等問題,使用的場景需要斟酌。記得前面一片博文簡單介紹了一下行級鎖的概念,其實本質上和樂觀鎖都是想要再資料庫層面加鎖控制併發,那麼什麼時候該用樂觀鎖,行級鎖,什麼時候得在程式級別加同步鎖,又要根據具體的業務場景去判斷。找到能夠滿足自己專案需求的方案,找到效能和可靠性的平衡點,才是一個程式設計師的價值所在。
歡迎大家關注、轉載、評論。
大家有什麼問題需要討論的也歡迎在評論下留言,我會隨時關注,大家一起進步。
參考部落格地址:
http://www.cnblogs.com/linjiqin/p/5096206.html
http://chenzhou123520.iteye.com/blog/1863407
http://blog.csdn.net/u013815546/article/details/54784025
http://blog.csdn.net/tzdwsy/article/details/47976919
相關推薦
mybatis和JPA實現樂觀鎖解決併發問題-阿里巴巴JAVA開發手冊詳細解讀
在阿里巴巴近期發出的阿里巴巴JAVA開發手冊(終極版)中有這樣一條記錄。 【強制】併發修改同一記錄時,避免更新丟失,需要加鎖。要麼在應用層加鎖,要麼在快取加鎖,要麼在資料庫層使用樂觀鎖,使用version作為更新依據。 說明:如果每次訪問衝突概率小於20%,推薦使用樂觀鎖
阿里巴巴Java開發手冊--程式碼抒寫規範和注意
Java中 VO、 PO、DO、DTO、 BO、 QO、DAO、POJO的概念 PO(persistant object) 持久物件 在 o/r 對映的時候出現的概念,如果沒有 o/r 對映,沒有這個概念存在了。通常對應資料模型 ( 資料庫 ), 本身還有部分業務邏輯的
阿里巴巴Java開發手冊———個人追加的見解和補充(一)
先上乾貨,《阿里巴巴Java開發手冊》的下載地址https://yq.aliyun.com/articles/69327下面分幾個部分對這個手冊進行說明,都是個人的見解,本人技術一般,如果有錯誤或者不妥,請評論指出,虛心接受,提前感謝了。 建議邊看手冊,邊食用以下說明,效果比較好。 前言首先當我第一次看見
為什麼阿里巴巴Java開發手冊中強制要求不要在foreach迴圈裡進行元素的remove和add操作?
在閱讀《阿里巴巴Java開發手冊》時,發現有一條關於在 foreach 迴圈裡進行元素的 remove/add 操作的規約,具體內容如下: 錯誤演示 我們首先在 IDEA 中編寫一個在 foreach 迴圈裡進行 remove 操作的程式碼: import java.util.ArrayList; imp
利用Redis實現分散式鎖 使用mysql樂觀鎖解決併發問題
寫在最前面 我在之前總結冪等性的時候,寫過一種分散式鎖的實現,可惜當時沒有真正應用過,著實的心虛啊。正好這段時間對這部分實踐了一下,也算是對之前填坑了。 分散式鎖按照網上的結論,大致分為三種:1、資料庫樂觀鎖; 2、基於Redis的分散式鎖;3.、基於ZooKeeper的分散式鎖; 關於樂觀鎖的實現其實
【Spring】27、JPA 實現樂觀鎖@Version註解的使用
線程並發 基礎上 nal where 本質 項目需求 得到 業務 -s 持久層使用jpa時,默認提供了一個註解@Version來實現樂觀鎖 簡單來說就是用一個version字段來充當樂觀鎖的作用。先來設計實體類 /** * Created by xujingfeng on
MP(MyBatis-Plus)實現樂觀鎖更新功能
## 實現步驟 **step1:新增樂觀鎖攔截器** MP的其他攔截器功能可以參考[官網](https://mp.baomidou.com/guide/interceptor.html) ```java @Bean public MybatisPlusInterceptor mybatisPlusInt
阿里巴巴Java開發規約外掛---------安裝和使用(健康之家)
1、 Eclispe安裝, Help -> Install New Software 2、輸入阿里規約外掛的網址https://p3c.alibaba.com/plugin/eclipse/update,Name隨便取一個(叫阿里巴巴都行),然後按照常規方
阿里巴巴Java開發規約-外掛使用[Idea和Eclipse]
阿里巴巴基於手冊內容,研發了一套自動化的IDE檢測外掛(IDEA、Eclipse)。該外掛在掃描程式碼後,將不符合規約的程式碼按Blocker(命名不符合規範)/Critical/Maj
分散式鎖解決併發的三種實現方式
分散式鎖解決併發的三種實現方式 在很多場景中,我們為了保證資料的最終一致性,需要很多的技術方案來支援,比如分散式事務、分散式鎖等。有的時候,我們需要保證一個方法在同 一時間內只能被同一個執行緒執行。在單機環境中,Java中其實提供了很多併發處理相關的API,但是這些API在分散式場景中就無能
你們部署伺服器是幾臺,併發量是多大;怎麼進行模擬搶購的同一時間請求量是多少;怎麼防止帶刷(黃牛)如果說部署兩臺伺服器 不同的程序 怎麼實現樂觀鎖?
Django專案用到5臺伺服器。部署在2臺上面,因為使用者量比較少。 模擬搶購主要解決2個問題: 1.高併發對資料庫產生的壓力 2.競爭狀態下如何解決庫存的正確減少("超賣"問題) 對於第一個問題可以使用redis解決,避免對資料庫的直接操作較少資料防護的查詢壓力。 對於“超賣”專案
悲觀鎖和樂觀鎖解決事務丟失跟新問題
事務丟失更新:A,B兩個事務通過id獲取資料,name:lisi,money:1000 首先,A事務修改name,把lisi變為zhangsan,然後提交事務。此時,B事務中不受A事務的影響,即B事務中的name還是lisi,此時如果B事務改變money為2000,然後提交事務。最後資料庫的資料
JPA使用樂觀鎖應對高併發
高併發系統的挑戰 在部署分散式系統時,我們通常把多個微服務部署在內網叢集中,再用API閘道器聚合起來對外提供。為了做負載均衡,通常會對每個微服務都啟動多個執行例項,通過註冊中心去呼叫。 那麼問題來了,因為有多個例項執行都是同一個應用,雖然微服務閘道器會把每一
Redis:多線程修改同一個Key使用watch+事務(mutil)實現樂觀鎖
width uno ... ack spool 場景 .html 高並發 遇到的問題 本篇文章是通過watch(監控)+mutil(事務)實現應用於在分布式高並發處理等相關場景。下邊先通過redis-cli.exe來測試多個線程修改時,遇到問題及解決問題。 高並發下修改同
elasticsearch 筆記七: es樂觀鎖的併發控制
1.併發控制 es 的併發控制是通過多version來實現的(不清楚樂觀鎖的自己提升去) 2.例項 //建立索引 PUT /test_index/test_type/7 { "test_field": "test test" } //返回建立結果 GET test_index
redis 實現樂觀鎖
轉載務必說明出處:https://blog.csdn.net/LiaoHongHB/article/details/83410650 1、redis通過事務機制中watch命令可以實現Java樂觀鎖機制 public void watch() { try {
利用Redis鎖解決併發問題
轉發 https://blog.csdn.net/fuyifang/article/details/83008884 用redis處理高併發是個很常見的方式,因為redis的訪問效率很高(直接訪問記憶體),一般我們會用來處理網站一瞬間的併發量。 那如果要使用redis來進行高併發問
Redis實現樂觀鎖
樂觀鎖 大多數是基於資料版本(version)的記錄機制實現的。即為資料增加一個版本標識,在基於資料庫表的版本解決方案中,一般是通過為資料庫表增加一個”version”欄位來實現讀取出資料時,將此版本號一同讀出,之後更新時,對此版本號
使用redis和zookeeper實現分散式鎖
1.分散式鎖 分散式鎖一般用在分散式系統或者多個應用中,用來控制同一任務是否執行或者任務的執行順序。在專案中,部署了多個tomcat應用,在執行定時任務時就會遇到同一任務可能執行多次的情況,我們可以藉助分散式鎖,保證在同一時間只有一個tomcat應用執行了定時任務。 &nb
springboot-通過註解和aop實現分散式鎖
一、原因 1、在分散式專案中,使用者觸發插入、更新等操作,我們只需要其中一個服務執行,如果不加分散式鎖,後果很嚴重 二、方法 1、分佈鎖一般通過redis實現,主要通過setnx函式向redis儲存一個key,value等於儲存時的時間戳,並設定過期時間,然後返回true; 2、