您好,登錄后才能下訂單哦!
mysql中的樂觀鎖是怎么實現的?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。而樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基于數據版本( Version )記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基于數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 "version" 字段來實現。讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大于數據庫表當前版本號,則予以更新,否則認為是過期數據。
優點:
從上面的例子可以看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷(操作員 A和操作員 B 操作過程中,都沒有對數據庫數據加鎖),大大提升了大并發量下的系統整體性能表現。
缺點:
需要注意的是,樂觀鎖機制往往基于系統中的數據存儲邏輯,因此也具備一定的局限性,如在上例中,由于樂觀鎖機制是在我們的系統中實現,來自外部系統的用戶余額更新操作不受我們系統的控制,因此可能會造成臟數據被更新到數據庫中。在系統設計階段,我們應該充分考慮到這些情況出現的可能性,并進行相應調整(如將樂觀鎖策略在數據庫存儲過程中實現,對外只開放基于此存儲過程的數據更新途徑,而不是將數據庫表直接對外公開)。
如何實現樂觀鎖呢,一般來說有以下2種方式:
1、使用數據版本(Version)記錄機制實現,這是樂觀鎖最常用的一種實現 方式。何謂數據版本?即為數據增加一個版本標識,一般是通過為數據庫表增加一個數字類型的 “version” 字段來實現。當讀取數據時,將version字段的值一同讀出,數據每更新一次,對此version值加一。
當我們提交更新的時候,判斷數據庫表對應記錄 的當前版本信息與第一次取出來的version值進行比對,如果數據庫表當前版本號與第一次取出來的version值相等,則予以更新,否則認為是過期數 據。用下面的一張圖來說明:
如上圖所示,如果更新操作順序執行,則數據的版本(version)依次遞增,不會產生沖突。但是如果發生有不同的業務操作對同一版本的數據進行修 改,那么,先提交的操作(圖中B)會把數據version更新為2,當A在B之后提交更新時發現數據的version已經被修改了,那么A的更新操作會失敗。
2、樂觀鎖定的第二種實現方式和第一種差不多,同樣是在需要樂觀鎖控制的table中增加一個字段,名稱無所謂,字段類型使用時間戳 (timestamp), 和上面的version類似,也是在更新提交的時候檢查當前數據庫中數據的時間戳和自己更新前取到的時間戳進行對比,如果一致則OK,否則就是版本沖突。
使用舉例:
以MySQL InnoDB
為例
還是拿之前的實例來舉:商品goods表中有一個字段status,status為1代表商品未被下單,status為2代表商品已經被下單,那么我們對某個商品下單時必須確保該商品status為1。假設商品的id為1。
下單操作包括3步驟:
1、查詢出商品信息
select (status,status,version) from t_goods where id=#{id}
2、根據商品信息生成訂單
3、修改商品status為2
update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
那么為了使用樂觀鎖,我們首先修改t_goods表,增加一個version字段,數據默認version值為1。
t_goods
表初始數據如下:
mysql> select * from t_goods; +----+--------+------+---------+ | id | status | name | version | +----+--------+------+---------+ | 1 | 1 | 道具 | 1 | | 2 | 2 | 裝備 | 2 | +----+--------+------+---------+ 2 rows in set mysql>
對于樂觀鎖的實現,我使用MyBatis來進行實踐,具體如下:
Goods實體類:
/** * ClassName: Goods <br/> * Function: 商品實體. <br/> * date: 2013-5-8 上午09:16:19 <br/> * @author chenzhou1025@126.com */ public class Goods implements Serializable { /** * serialVersionUID:序列化ID. */ private static final long serialVersionUID = 6803791908148880587L; /** * id:主鍵id. */ private int id; /** * status:商品狀態:1未下單、2已下單. */ private int status; /** * name:商品名稱. */ private String name; /** * version:商品數據版本號. */ private int version; @Override public String toString(){ return "good id:"+id+",goods status:"+status+",goods name:"+name+",goods version:"+version; } //setter and getter }
GoodsDao
/** * updateGoodsUseCAS:使用CAS(Compare and set)更新商品信息. <br/> * * @author chenzhou1025@126.com * @param goods 商品對象 * @return 影響的行數 */ int updateGoodsUseCAS(Goods goods);
mapper.xml
<update id="updateGoodsUseCAS" parameterType="Goods"> <![CDATA[ update t_goods set status=#{status},name=#{name},version=version+1 where id=#{id} and version=#{version} ]]> </update>
GoodsDaoTest測試類
@Test public void 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?"成功":"失敗")); }
輸出結果:
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
表中數據如下:
mysql> select * from t_goods; +----+--------+------+---------+ | id | status | name | version | +----+--------+------+---------+ | 1 | 2 | 道具 | 2 | | 2 | 2 | 裝備 | 2 | +----+--------+------+---------+ 2 rows in set mysql>
我們可以看到 id為1的數據version已經在第一次更新時修改為2了。所以我們更新good2時update where條件已經不匹配了,所以更新不會成功,具體sql如下:
update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
這樣我們就實現了樂觀鎖。
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。