1. 程式人生 > >樂觀鎖 悲觀鎖例項

樂觀鎖 悲觀鎖例項

  在多使用者環境中,在同一時間可能會有多個使用者更新相同的記錄,這會產生衝突。這就是著名的併發性問題。

典型的衝突有:

  • 丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:使用者A把值從6改為2,使用者B把值從2改為6,則使用者A丟失了他的更新。
  • 髒讀:當一個事務讀取其它完成一半事務的記錄時,就會發生髒讀取。例如:使用者A,B看到的值都是6,使用者B把值改為2,使用者A讀到的值仍為6。

為了解決這些併發帶來的問題。 我們需要引入併發控制機制。

併發控制機制

  悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作。[1]

  樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。[1] 樂觀鎖不能解決髒讀的問題。

樂觀鎖應用

樂觀鎖介紹:

  樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認為資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則讓返回使用者錯誤的資訊,讓使用者決定如何去做。那麼我們如何實現樂觀鎖呢,一般來說有以下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表初始資料如下:

  對於樂觀鎖的實現,我使用MyBatis來進行實踐,具體如下:

Goods實體類:

複製程式碼
/**
 * ClassName: Goods <br/>
 * Function: 商品實體. <br/>*/
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)更新商品資訊

 * @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> 
複製程式碼

  我們可以看到 id1的資料version已經在第一次更新時修改為2了。所以我們更新good2update where條件已經不匹配了,所以更新不會成功,具體sql如下:

update t_goods 
set status=2,version=version+1
where id=#{id} and version=#{version};

  這樣我們就實現了樂觀鎖

悲觀鎖應用

需要使用資料庫的鎖機制,比如SQL SERVER 的TABLOCKX(排它表鎖) 此選項被選中時,SQL  Server  將在整個表上置排它鎖直至該命令或事務結束。這將防止其他程序讀取或修改表中的資料。

SqlServer中使用

Begin Tran
select top 1 @TrainNo=T_NO
         from Train_ticket   with (UPDLOCK)   where S_Flag=0

      update Train_ticket
         set T_Name=user,
             T_Time=getdate(),
             S_Flag=1
         where [email protected]
commit

我們在查詢的時候使用了with (UPDLOCK)選項,在查詢記錄的時候我們就對記錄加上了更新鎖,表示我們即將對此記錄進行更新. 注意更新鎖和共享鎖是不衝突的,也就是其他使用者還可以查詢此表的內容,但是和更新鎖和排它鎖是衝突的.所以其他的更新使用者就會阻塞.

結論

  在實際生產環境裡邊,如果併發量不大且不允許髒讀,可以使用悲觀鎖解決併發問題;但如果系統的併發非常大的話,悲觀鎖定會帶來非常大的效能問題,所以我們就要選擇樂觀鎖定的方法.

相關推薦

樂觀 悲觀例項

  在多使用者環境中,在同一時間可能會有多個使用者更新相同的記錄,這會產生衝突。這就是著名的併發性問題。 典型的衝突有: 丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:使用者A把值從6改為2,使用者B把值從2改為6,則使用者A丟失了他的更新。髒讀:當一個事務讀取其它完成一半事務

AtomicInteger如何保證執行緒安全以及樂觀/悲觀的概念

最近面試被問到一個問題,AtomicInteger如何保證執行緒安全?我查閱了資料 發現還可以引申到 樂觀鎖/悲觀鎖的概念,覺得值得一記。 眾所周知,JDK提供了AtomicInteger保證對數字的操作是執行緒安全的,執行緒安全我首先想到了synchronized和Lock,但是這

最簡單理解Mysql共享、排他樂觀悲觀

共享鎖 select * from xx where id = 10 lock in share mode 排他鎖 select * from xx where id = 10 for update 樂觀鎖 select num,version from xx where id = 10

樂觀悲觀,CAS,volatile

悲觀鎖 假設最壞的情況,每次去讀取資料都認為別人會修改資料,所以可能會產生併發,於是每次在拿資料的時候都要上鎖。Java裡面的同步原語synchronized關鍵字的實現也是悲觀鎖。 樂觀鎖 就是每次拿資料的時候認為不會有人來修改資料,所以不上鎖,但是在更新的時候會判斷此期間是否

事務 悲觀 樂觀 概念 應用場景 使用方式 小記

【部落格園cnblogs筆者m-yb原創(部分引用, 在文末有註明),轉載請加本文部落格連結,筆者github: https://github.com/mayangbo666,公眾號aandb7,QQ群927113708】  https://www.cnblogs.com/m-yb/p/99749

圖解 --樂觀 悲觀 可重入 獨佔 共享 公平 非公平

1.樂觀鎖--樂觀鎖是一種思想,它只解決對共享資源更新時的一致性問題,不解決讀取共享資源過程中,其他執行緒修改了共享資源導致讀取的是舊的資源的問題 一般正規化為: private Node enq(final Node node) { for (;;) { N

JAVA併發 - 悲觀VS樂觀(一)

文章目錄 悲觀鎖VS樂觀鎖 1.悲觀鎖 1.1什麼是悲觀鎖 1.2原始碼分析 synchronized Lock 1.3應用場景 1.4實現

悲觀樂觀、行級、表級 悲觀樂觀、行級、表級

轉載自:http://www.cnblogs.com/xhybk/p/9278684.html 悲觀鎖、樂觀鎖、行級鎖、表級鎖   更新丟失:新改的覆蓋先改的,開發中有三種方法解決 1、將事務級別提高到最高級別TRANSACT

資料庫 樂觀/悲觀 的介紹及應用

在處理併發問題時,我們會經常遇到以下2點問題 更新丟失. 如:使用者A把值從 ‘一等獎’ 改為 ‘二等獎’,使用者B把值從 ‘一等獎’ 改為 ‘三等獎’,則使用者A的更新被覆蓋. 髒讀. 如:使用者A,B看到的值都是 ‘一等獎’ ,使用者B把值改為 ‘二等獎

Java併發 行級/欄位/表級 樂觀/悲觀 共享/排他[轉]

前言 鎖是防止在兩個事務操作同一個資料來源(表或行)時互動破壞資料的一種機制。 資料庫採用封鎖技術保證併發操作的可序列性。 以Oracle為例: Oracle的鎖分為兩大類:資料鎖(也稱DML鎖)和字典鎖。 字典鎖是Oracle DBMS內部用於對字典表的封鎖。 字典鎖包括語

Java高效併發之樂觀悲觀、(互斥同步、非互斥同步)

樂觀鎖和悲觀鎖 首先我們理解下兩種不同思路的鎖,樂觀鎖和悲觀鎖。 這兩種鎖機制,是在多使用者環境併發控制的兩種所機制。下面看百度百科對樂觀鎖和悲觀鎖兩種鎖機制的定義: 樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖

獨享/共享+公平/非公平+樂觀/悲觀

本文標題:最全Java鎖詳解:獨享鎖/共享鎖+公平鎖/非公平鎖+樂觀鎖/悲觀鎖  轉載請保留頁面地址:http://youzhixueyuan.com/detailed-explanation-of-java-lock.html 在Java併發場景中,會涉及到各種各樣的鎖如公平鎖,樂觀

最全Java詳解:獨享/共享+公平/非公平+樂觀/悲觀

最全Java鎖詳解:獨享鎖/共享鎖+公平鎖/非公平鎖+樂觀鎖/悲觀鎖 樂觀鎖 VS 悲觀鎖 1.樂觀鎖 2.悲觀鎖 3.總之 公平鎖 VS 非公平鎖 1.公平鎖 2.非公平鎖 3.典型應用

HIbernate——事務併發問題及處理(樂觀悲觀

一、事物併發三種常見問題 1.dirty read 髒讀 時間 事物A 事物B T1 開始事物 開始事物 T2 查詢賬戶餘額為1000 T3 匯入100把餘額變為1100 T4

淺談Oracle機制表樂觀悲觀

  Oracle的鎖機制主要分為行鎖和表鎖,行鎖即鎖定表中的某行資料,表鎖鎖定表中所有資料。鎖定的資料不能插入,更新,刪除,只能查詢,語法 for update。鎖的週期為一次資料提交,一次資料提交中可能會有多條SQL語句。   在大併發中為了保證某些資料的唯

java 樂觀悲觀專案實際應用

悲觀鎖:簡單的理解就是把需要的資料全部加鎖,在事務提交之前,這些資料全部不可讀取和修改。 樂觀鎖:使用對資料進行版本校驗和比較,來對保證本次的更新時最新的,否則就失敗。 ------樂觀鎖  通過JPA 表中加入 @Version JPA通過在實體類(POJO)中使用@

MySQL 樂觀 悲觀 共享 排他

才會 鎖定 角度 lec 回滾 等待 mysq 其他 字段 樂觀鎖 樂觀鎖是邏輯概念上的鎖,不是數據庫自帶的,需要我們自己去實現。樂觀鎖是指操作數據庫時(更新操作),想法很樂觀,認為這次的操作不會導致沖突,在操作數據時,並不進行任何其他的特殊處理(也就是不加鎖),而在進行更

資料庫中的行、表樂觀悲觀

樂觀鎖、悲觀鎖:http://blog.csdn.net/hongchangfirst/article/details/26004335 行鎖、表鎖:http://blog.sina.com.cn/s/blog_703074da0101ghsh.html

hibernate版本控制 樂觀悲觀

//1: 資料庫表 people 加上一列  version int //2:  實體類  people 屬性 ( private  int version;) //3:  在hibernate 對映檔案class 之間  在一個表示副 id 之後加上  <version    cloumn="vers

解決並發問題,數據庫常用的兩把——悲觀樂觀

weixin 自己 解決 如果 cat and 機制 同事 進行 一、概念: 樂觀鎖:適用於寫少讀多的情景,因為這種樂觀鎖相當於java的cas(比較並替換),所以多條數據同事過來的時候不用等待,可以立即進行返回 悲觀鎖:適用於寫多讀少的情景,這種情況也相當於java的