1. 程式人生 > >併發控制中的樂觀鎖與悲觀鎖

併發控制中的樂觀鎖與悲觀鎖

為什麼需要鎖(併發控制)?

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

典型的衝突有:
(1)丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:使用者A把值從6改為2,使用者B把值從2改為6,則使用者A丟失了他的更新。

(2)髒讀:當一個事務讀取其它完成一半事務的記錄時,就會發生髒讀取。例如:使用者A,B看到的值都是6,使用者B把值改為2,使用者A讀到的值仍為6。

併發控制機制

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

樂觀鎖與悲觀鎖

悲觀鎖(Pessimistic Lock),就是很悲觀,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會block直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。

樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號等機制。樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖。

兩種鎖各有優缺點
不可認為一種好於另一種,像樂觀鎖適用於寫比較少的情況下,即衝突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果經常產生衝突,上層應用會不斷的進行retry,這樣反倒是降低了效能,所以這種情況下用悲觀鎖就比較合適。

樂觀鎖應用

  1. 使用自增長的整數表示資料版本號。更新時檢查版本號是否一致,比如資料庫中資料版本為6,更新提交時version=6+1,使用該version值(=7)與資料庫version+1(=7)作比較,如果相等,則可以更新,如果不等則有可能其他程式已更新該記錄,所以返回錯誤。

  2. 使用時間戳來實現.
    注:對於以上兩種方式,Hibernate自帶實現方式:在使用樂觀鎖的欄位前加annotation: @Version, Hibernate在更新時自動校驗該欄位。

悲觀鎖應用

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

在實際生產環境裡邊,如果併發量不大且不允許髒讀,可以使用悲觀鎖解決併發問題;但如果系統的併發非常大的話,悲觀鎖定會帶來非常大的效能問題,所以我們就要選擇樂觀鎖定的方法.
悲觀鎖會造成訪問資料庫時間較長,併發性不好,特別是長事務。
樂觀鎖在現實中使用得較多,廠商較多采用。

一個典型的倚賴資料庫的悲觀鎖呼叫:

select * from account where name=”Erica” for update

這條 sql 語句鎖定了account 表中所有符合檢索條件( name=”Erica” )的記錄。本次事務提交之前(事務提交時會釋放事務過程中的鎖),外界無法修改這些記錄。

Hibernate 的悲觀鎖,也是基於資料庫的鎖機制實現。

注意,只有在查詢開始之前(也就是Hiberate 生成 SQL 之前)設定加鎖,才會真正通過資料庫的鎖機制進行加鎖處理,否則,資料已經通過不包含 for update子句的 Select SQL 載入進來,所謂資料庫加鎖也就無從談起。

樂觀鎖( Optimistic Locking )相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠資料庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是資料庫效能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。

經典案例分析

如一個金融系統,當某個操作員讀取使用者的資料,並在讀出的使用者資料的基礎上進行修改時(如更改使用者帳戶餘額),如果採用悲觀鎖機制,也就意味著整個操作過程中(從操作員讀出資料、開始修改直至提交修改結果的全過程,甚至還包括操作員中途去煮咖啡的時間),資料庫記錄始終處於加鎖狀態,可以想見,如果面對幾百上千個併發,這樣的情況將導致怎樣的後果。

樂觀鎖機制在一定程度上解決了這個問題。

樂觀鎖,大多是基於資料版本( Version )記錄機制實現。何謂資料版本?即為資料增加一個版本標識,在基於資料庫表的版本解決方案中,一般是通過為資料庫表增加一個 “version” 欄位來實現。

讀取出資料時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交資料的版本資料與資料庫表對應記錄的當前版本資訊進行比對,如果提交的資料版本號大於資料庫表當前版本號,則予以更新,否則認為是過期資料。

對於上面修改使用者帳戶資訊的例子而言,假設資料庫中帳戶資訊表中有一個version 欄位,當前值為 1 ;
而當前帳戶餘額欄位( balance )為 $100 。

1 操作員 A 此時將其讀出( version=1 ),並從其帳戶餘額中扣除 50100-$50 )。

2 在操作員 A 操作的過程中,操作員 B 也讀入此使用者資訊( version=1 ),並從其帳戶餘額中扣除 20100-$20 )。

3 操作員 A 完成了修改工作,將資料版本號加一( version=2 ),連同帳戶扣除後餘額( balance=$50 ),提交至資料庫更新,此時由於提交資料版本大於資料庫記錄當前版本,資料被更新,資料庫記錄 version 更新為 2 。

4 操作員 B 完成了操作,也將版本號加一( version=2 )試圖向資料庫提交資料( balance=$80 ),但此時比對資料庫記錄版本時發現,操作員 B 提交的資料版本號為 2 ,資料庫記錄當前版本也為 2 ,不滿足 “ 提交版本必須大於記錄當前版本才能執行更新 “ 的樂觀鎖策略,因此,操作員 B 的提交被駁回。這樣,就避免了操作員 B 用基於 version=1 的舊資料修改的結果覆蓋操作員 A 的操作結果的可能。

總結

從上面的例子可以看出,樂觀鎖機制避免了長事務中的資料庫加鎖開銷(操作員 A和操作員 B 操作過程中,都沒有對資料庫資料加鎖),大大提升了大併發量下的系統整體效能表現。

需要注意的是,樂觀鎖機制往往基於系統中的資料儲存邏輯,因此也具備一定的侷限性,如在上例中,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的使用者餘額更新操作不受我們系統的控制,因此可能會造成髒資料被更新到資料庫中。在系統設計階段,我們應該充分考慮到這些情況出現的可能性,並進行相應調整(如將樂觀鎖策略在資料庫儲存過程中實現,對外只開放基於此儲存過程的資料更新途徑,而不是將資料庫表直接對外公開)。

談到悲觀鎖和樂觀鎖,就要談到資料庫的併發問題,資料庫的隔離級別越高併發性就越差

重點內容併發性:當前系統進行了序列化後,你讀取資料庫後,別人查詢不了,稱為併發性不好

1.悲觀鎖
具有排它性(我鎖住當前資料後,比人看不到此資料),悲觀鎖一般是由資料庫機制來做到的

悲觀鎖的實現:

通常依賴於資料庫機制,在整修過程中將資料庫鎖定,其它任何使用者都不能讀取或修改

悲觀鎖的適用場景:
悲觀鎖一般適合短事物比較多(如某一個數據取出後加1,立即釋放)

例項:
這裡寫圖片描述

使用者1,使用者2同時讀取到資料,但是使用者2先-200,這時資料庫裡的是800,現在使用者1也開始-200,可以使用者1剛才讀取到的資料是1000,現在使用者用剛一開始讀取的資料1000-200,而使用者1在更新時資料庫裡的資料是800,按理說使用者1應該是800-200=600,這樣就造成更新丟失。這種情況下可採用兩種

方式解決:悲觀鎖、樂觀鎖。
悲觀鎖:使用者1讀取資料後,用鎖將其讀取的資料鎖上,這時使用者2是讀取不到資料的,只有使用者1釋放鎖後用戶2才可以讀取,同樣使用者2讀取資料的資料也鎖上,這樣就可以解決更新丟失了。

實體類:

public class Inventory {
    private int itemNo; 
    private String itemName;    
    private int quantity;
    public int getItemNo() {
        return itemNo;
    }
    public void setItemNo(int itemNo) {
        this.itemNo = itemNo;
    }
    public String getItemName() {
        return itemName;
    }
    public void setItemName(String itemName) {
        this.itemName = itemName;
    }
    public int getQuantity() {
        return quantity;
    }
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }   
}

對映檔案:

<hibernate-mapping>
    <class name="com.cn.hibernate.Inventory" table="t_inventory">
        <id name="itemNo">
            <generator class="native"/>
        </id>
        <property name="itemName"/>
        <property name="quantity"/>
    </class>
</hibernate-mapping>

悲觀鎖的使用:

如果要使用悲觀鎖,肯定在載入資料時就要鎖住,通常採用for update語句

Hibernate使用load進行悲觀鎖載入

Session.load(Class arg(),Serializable arg1,LockMode arg2)throws HibernateException

LockMode:悲觀鎖模式(一般使用LockMode.UPGRADE)

session = HibernateUtils.getSession();
            tx = session.beginTransaction();
            Inventory inv = (Inventory)session.load(Inventory.class, 1, LockMode.UPGRADE);
            System.out.println(inv.getItemName());
            inv.setQuantity(inv.getQuantity()-200);

            session.update(inv);
            tx.commit();

如果使用悲觀鎖,那麼lazy(懶載入)無效

2.樂觀鎖
樂觀鎖:不是鎖,是一種衝突檢測機制,樂觀鎖的併發性較好,因為我改的時候,別人可隨便修改樂觀鎖的實現方式:常用的是版本的方式(每個資料表中有一個版本欄位version,某一個使用者更新資料庫後,版本號+1,另一個使用者修改後再+1,當用戶更新發現資料庫當前版本號與讀取資料時版本號不一致,等於或小於資料庫版本號則更新不了)

Hibernate使用樂觀鎖需要在對映檔案中配置才可生效

實體類

public class Inventory {
    private int itemNo; 
    private String itemName;    
    private int quantity;   
    private int version;//Hibernate使用者實現版本方式樂觀鎖,但需要在對映檔案中配置
    public int getItemNo() {
        return itemNo;
    }
    public void setItemNo(int itemNo) {
        this.itemNo = itemNo;
    }
    public String getItemName() {
        return itemName;
    }
    public void setItemName(String itemName) {
        this.itemName = itemName;
    }
    public int getQuantity() {
        return quantity;
    }
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
    public int getVersion() {
        return version;
    }
    public void setVersion(int version) {
        this.version = version;
    }
}

相關推薦

併發控制樂觀悲觀

為什麼需要鎖(併發控制)? 在多使用者環境中,在同一時間可能會有多個使用者更新相同的記錄,這會產生衝突。這就是著名的併發性問題。 典型的衝突有: (1)丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:使用者A把值從6改為2,使用

資料庫樂觀悲觀

樂觀鎖: 在關係資料庫管理系統裡,樂觀併發控制(又名“樂觀鎖”,Optimistic Concurrency Control,縮寫“OCC”)是一種併發控制的方法。它假設多使用者併發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分資料。在提交資料更新之前,

Java併發問題--樂觀悲觀

首先為什麼需要鎖(併發控制)? 在多使用者環境中,在同一時間可能會有多個使用者更新相同的記錄,這會產生衝突。這就是著名的併發性問題。 典型的衝突有: 丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:使用者A把值從6改為2,使用者B把值從2改為

【轉】Java併發問題--樂觀悲觀以及樂觀的一種實現方式-CAS

首先介紹一些樂觀鎖與悲觀鎖: 悲觀鎖:總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上

Java併發問題--樂觀悲觀以及樂觀的一種實現方式-CAS

首先介紹一些樂觀鎖與悲觀鎖:   悲觀鎖:總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。

基於Django的樂觀悲觀解決訂單併發問題的一點淺見

然後就是樂觀鎖查詢了,相比悲觀鎖,樂觀鎖其實並不能稱為是鎖,那麼它是在做什麼事情呢。其實是在你要進行資料庫操作時先去查詢一次資料庫中商品的庫存,然後在你要更新資料庫中商品庫存時,將你一開始查詢到的庫存數量和商品的ID一起作為更新的條件,當受影響行數返回為0時,說明沒有修改成功,那麼就是說別的程序修改了該資料,

樂觀悲觀

到你 目前 from 提高 選中 base 排它鎖 之前 準備 在多用戶環境中,在同一時間可能會有多個用戶更新相同的記錄,這會產生沖突。這就是著名的並發性問題。 典型的沖突有: l 丟失更新:一個事務的更新覆蓋了其它事務的更新結果,就是所謂的更新丟失。例如:用戶A把值從6改

樂觀悲觀的簡單區分

個數 行數 但是 分布式系 修改 讀寫 使用場景 狀態 控制 1、鎖的出現,是因為並發讀寫同一個數據的時候,需要進行數據完備性的保護,避免臟讀、臟寫等。 2、樂觀鎖,需要在事務中加鎖,在讀取數據的時候,不必在意數據是否已經被修改了(即允許臟讀);但是在寫入數據的時候,要檢查

[數據庫事務]詳解七: 深入理解樂觀悲觀

ood insert 影響 hiberna memcach begin 策略 goods 其它 註明: 本文轉載自http://www.hollischuang.com/archives/934在數據庫的鎖機制中介紹過,數據庫管理系統(DBMS)中的並發控制的任務是確保在

Java並發問題--樂觀悲觀以及樂觀的一種實現方式-CAS

RF -- 指針 locking water 更多 錯誤 創建 判斷 首先介紹一些樂觀鎖與悲觀鎖: 悲觀鎖:總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。傳統的關系型數據庫裏邊就用到了很多這

mysql的樂觀悲觀

想要 附加 情況 屬性 ... str 但是 share 版本 樂觀鎖 總是認為不會產生並發問題,每次去取數據的時候總認為不會有其他線程對數據進行修改,因此不會上鎖,但是在更新時會判斷其他線程在這之前有沒有對數據進行修改,一般會使用版本號機制或CAS操作實現。 例如: 有這

深入理解樂觀悲觀

遇到 實現 個數 默認 ODB date 開始 安全 行數 前言在數據庫的鎖機制中介紹過,數據庫管理系統(DBMS)中的並發控制的任務是確保在多個事務同時存取數據庫中同一數據時不破壞事務的隔離性和統一性以及數據庫的統一性。 樂觀並發控制(樂觀鎖)和悲觀並發控制(悲觀鎖)是並

Java多線程系列---“基礎篇”13之 樂觀悲觀

而是 關系型 lock color 情況 發現 mis 再次 中一 轉自:http://www.cnblogs.com/zhengbin/p/5657435.html 樂觀鎖   樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認

資料庫 樂觀悲觀

樂觀鎖 總是認為不會產生併發問題,每次去取資料的時候總認為不會有其他執行緒對資料進行修改,因此不會上鎖,但是在更新時會判斷其他執行緒在這之前有沒有對資料進行修改,一般會使用版本號機制或CAS操作實現。 version方式:一般是在資料表中加上一個資料版本號version欄位,表示資料被修改的次數,當

最通俗易懂的樂觀悲觀原理及實現

一、樂觀鎖   總是認為不會產生併發問題,每次去取資料的時候總認為不會有其他執行緒對資料進行修改,因此不會上鎖,但是在更新時會判斷其他執行緒在這之前有沒有對資料進行修改,一般會使用版本號機制或CAS操作實現。  version方式:一般是在資料表中加上一個資料版本號ver

什麼是樂觀悲觀

樂觀鎖: 簡單的來說:就是認為別人不會過來修改它的資料,常見的樂觀鎖通常會帶一個version(版本),等到提交的時候,會去檢查一下版本,如果版本修改了,就會丟擲異常,並且回滾資料; 樂觀鎖通常需要在表中額外設計一個version的冗餘欄位 並且在插入資料的時候,將version初始

關於樂觀悲觀的實際應用

開門見山,先聊一聊我實際遇到的業務問題: 在專案中有一個競猜下注的功能,它的賠率是根據A隊和B隊兩邊的下注總金額來計算的。於是當有使用者下注某一邊時,兩邊的賠率都會進行相應的變化。 反應到資料庫裡就是(簡化版本),一個人下注,會更改資料庫盤口表的幾個欄位:A隊賠率,A隊下注金額、B隊賠率,B隊下注金額

深入Mysql機制(四)樂觀悲觀

深入Mysql鎖機制(四)樂觀鎖與悲觀鎖 在資料庫鎖機制中介紹過,資料庫管理系統(DBMS)中的併發控制的任務是確保在多個事務同時存取資料庫中同一資料時不破壞事務的隔離性和統一性以及資料庫的統一性。 樂觀併發控制(樂觀鎖)和悲觀併發控制(悲觀鎖)是併發控制主要採用的技術手段。

關於樂觀悲觀的應用解決方案

原文連結 https://juejin.im/post/5c0009adf265da614a3a3741 在專案中有一個競猜下注的功能,它的賠率是根據A隊和B隊兩邊的下注總金額來計算的。於是當有使用者下注某一邊時,兩邊的賠率都會進行相應的變化。 反應到資料庫裡就是(簡化版本),一個

樂觀悲觀及應用舉例

最近因為在工作中需要,學習了樂觀鎖與悲觀鎖的相關知識,這裡我通過這篇文章,把我自己對這兩個“鎖家”兄弟理解記錄下來;        - 悲觀鎖:正如其名,它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)的修改持保守態度,因此,在整個資料處理過程