樂觀鎖、悲觀鎖和MVCC,今天讓你一次搞懂

** | 做者 肖澤凡,騰訊TEG研發管理部小小後臺攻城獅一枚,負責騰訊敏捷產品研發平臺TAPD的基礎功能的開發和維護,熱愛技術,喜歡分享,文章首次發表於SegmentFault,博客名「X先生」,歡迎與我交流**算法

在數據庫的實際使用過程當中,咱們經常會遇到不但願數據被同時寫或者讀的情景,例如秒殺場景下,兩個請求同時讀到系統還有庫存1個,而後又前後把庫存更新爲0,這時候就會出現超賣的狀況,這時候貨物的實際庫存和咱們的記錄就會對應不上了。數據庫

爲了解決這種資源競爭致使的數據不一致等問題,咱們須要有一種機制來進行保證數據的正確訪問和修改,而在數據庫中,這種機制就是數據庫的併發控制。其中樂觀併發控制,悲觀併發控制和多版本併發控制是數據庫併發控制主要採用的技術手段。編程

悲觀併發控制

1、本質安全

維基百科:在關係數據庫管理系統裏,悲觀併發控制(又名「悲觀鎖」,PessimisticConcurrency Control,縮寫「PCC」)是一種併發控制的方法。它能夠阻止一個事務以影響其餘用戶的方式來修改數據。若是一個事務執行的操做讀某行數據應用了鎖,那只有當這個事務把鎖釋放,其餘事務纔可以執行與該鎖衝突的操做。多線程

事實上咱們常說的悲觀鎖並非一種實際的鎖,而是一種併發控制的思想,悲觀併發控制對於數據被修改持悲觀的態度,認爲數據被外界訪問時,必然會產生衝突,因此在數據處理的過程當中都採用加鎖的方式來保證對資源的獨佔。併發

數據庫的鎖機制其實都是基於悲觀併發控制的觀點進行實現的,並且按照實際使用狀況,數據庫的鎖又能夠分爲許多種類,具體能夠見我後面的文章。性能

2、實現方式atom

數據庫悲觀鎖的加鎖流程大體以下:線程

  1. 開始事務後,按照操做類型給須要加鎖的數據申請加某一類鎖:例如共享行鎖等設計

  2. 加鎖成功則繼續後面的操做,若是數據已經被加了其餘的鎖,並且和如今要加的鎖衝突,則會加鎖失敗(例如已經加了排他鎖),此時需等待其餘的鎖釋放(可能出現死鎖)

  3. 完成事務後釋放所加的鎖

3、優缺點

優勢:

悲觀併發控制採起的是保守策略:「先取鎖,成功了才訪問數據」,這保證了數據獲取和修改都是有序進行的,所以適合在寫多讀少的環境中使用。固然使用悲觀鎖沒法維持很是高的性能,可是在樂觀鎖也沒法提供更好的性能前提下,悲觀鎖卻能夠作到保證數據的安全性。

缺點:

因爲須要加鎖,並且可能面臨鎖衝突甚至死鎖的問題,悲觀併發控制增長了系統的額外開銷,下降了系統的效率,同時也會下降了系統的並行性。

樂觀併發控制

1、本質

維基百科:在關係數據庫管理系統裏,樂觀併發控制(又名「樂觀鎖」,OptimisticConcurrency Control,縮寫「OCC」)是一種併發控制的方法。它假設多用戶併發的事務在處理時不會彼此互相影響,各事務可以在不產生鎖的狀況下處理各自影響的那部分數據。

樂觀併發控制對數據修改持樂觀態度,認爲即便在併發環境中,外界對數據的操做通常是不會形成衝突,因此並不會去加鎖,而是在提交數據更新以前,每一個事務會先檢查在該事務讀取數據後,有沒有其餘事務又修改了該數據。若是其餘事務有更新的話,則讓返回衝突信息,讓用戶決定如何去作下一步,好比說重試或者回滾。

能夠看出,樂觀鎖其實也不是實際的鎖,甚至沒有用到鎖來實現併發控制,而是採起其餘方式來判斷可否修改數據。樂觀鎖通常是用戶本身實現的一種鎖機制,雖然沒有用到實際的鎖,可是能產生加鎖的效果。

2、實現方式

CAS(比較與交換,Compare and swap) 是一種有名的無鎖算法。無鎖編程,即不使用鎖的狀況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的狀況下實現變量的同步,因此也叫非阻塞同步(Non-blocking Synchronization)。實現非阻塞同步的方案稱爲「無鎖編程算法」( Non-blocking algorithm)。

樂觀鎖基本都是基於 CAS(Compare and swap)算法來實現的。咱們先來看下CAS過程,一個CAS操做的過程能夠用如下c代碼表示:

intcas(long*addr,longold,longnew)
{
    /* Executes atomically. */
    if(*addr!= old)
        return0;
    *addr= new;
    return1;
}

CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。整個CAS操做是一個原子操做,是不可分割的。

樂觀鎖的實現就相似於上面的過程,主要有如下幾種方式:

  1. 版本號標記:在表中新增一個字段:version,用於保存版本號。獲取數據的時候同時獲取版本號,而後更新數據的時候用如下命令:updatexxx set version=version+1,… where … version="old version" and ....。這時候經過判斷返回結果的影響行數是否爲0來判斷是否更新成功,更新失敗則說明有其餘請求已經更新了數據了。

  2. 時間戳標記:和版本號同樣,只是經過時間戳來判斷。通常來講不少數據表都會有更新時間這一個字段,經過這個字段來判斷就不用再新增一個字段了。

  3. 待更新字段:若是沒有時間戳字段,並且不想新增字段,那能夠考慮用待更新字段來判斷,由於更新數據通常都會發生變化,那更新前能夠拿要更新的字段的舊值和數據庫的現值進行比對,沒有變化則更新。

  4. 全部字段標記:數據表全部字段都用來判斷。這種至關於就、不只僅對某幾個字段作加鎖了,而是對整個數據行加鎖,只要本行數據發生變化,就不進行更新。

3、優缺點

優勢:

樂觀併發控制沒有實際加鎖,因此沒有額外開銷,也不錯出現死鎖問題,適用於讀多寫少的併發場景,由於沒有額外開銷,因此能極大提升數據庫的性能。

缺點:

樂觀併發控制不適合於寫多讀少的併發場景下,由於會出現不少的寫衝突,致使數據寫入要屢次等待重試,在這種狀況下,其開銷其實是比悲觀鎖更高的。並且樂觀鎖的業務邏輯比悲觀鎖要更爲複雜,業務邏輯上要考慮到失敗,等待重試的狀況,並且也沒法避免其餘第三方系統對數據庫的直接修改的狀況。

多版本併發控制

1、本質

維基百科: 多版本併發控制(Multiversion concurrency control, MCC 或 MVCC),是數據庫管理系統經常使用的一種併發控制,也用於程序設計語言實現事務內存。

樂觀併發控制和悲觀併發控制都是經過延遲或者終止相應的事務來解決事務之間的競爭條件來保證事務的可串行化;雖然前面的兩種併發控制機制確實可以從根本上解決併發事務的可串行化的問題,可是其實都是在解決寫衝突的問題,二者區別在於對寫衝突的樂觀程度不一樣(悲觀鎖也能解決讀寫衝突問題,可是性能就通常了)。而在實際使用過程當中,數據庫讀請求是寫請求的不少倍,咱們若是能解決讀寫併發的問題的話,就能更大地提升數據庫的讀性能,而這就是多版本併發控制所能作到的事情。

與悲觀併發控制和樂觀併發控制不一樣的是,MVCC是爲了解決讀寫鎖形成的多個、長時間的讀操做餓死寫操做問題,也就是解決讀寫衝突的問題。MVCC 能夠與前二者中的任意一種機制結合使用,以提升數據庫的讀性能。

數據庫的悲觀鎖基於提高併發性能的考慮,通常都同時實現了多版本併發控制。不只是MySQL,包括Oracle、PostgreSQL等其餘數據庫系統也都實現了MVCC,但各自的實現機制不盡相同,由於MVCC沒有一個統一的實現標準。

總的來講,MVCC的出現就是數據庫不滿用悲觀鎖去解決讀-寫衝突問題,因性能不高而提出的解決方案。

2、實現方式

MVCC的實現,是經過保存數據在某個時間點的快照來實現的。每一個事務讀到的數據項都是一個歷史快照,被稱爲快照讀,不一樣於當前讀的是快照讀讀到的數據可能不是最新的,可是快照隔離能使得在整個事務看到的數據都是它啓動時的數據狀態。而寫操做不覆蓋已有數據項,而是建立一個新的版本,直至所在事務提交時才變爲可見。

當前讀和快照讀

什麼是MySQL InnoDB下的當前讀和快照讀?

當前讀

像select lock in share mode(共享鎖),select for update ; update, insert ,delete(排他鎖)這些操做都是一種當前讀,爲何叫當前讀?就是它讀取的是記錄的最新版本,讀取時還要保證其餘併發事務不能修改當前記錄,會對讀取的記錄進行加鎖。

快照讀

像不加鎖的select操做就是快照讀,即不加鎖的非阻塞讀;快照讀的前提是隔離級別不是未提交讀和串行化級別,由於未提交讀老是讀取最新的數據行,而不是符合當前事務版本的數據行。而串行化則會對全部讀取的行都加鎖

3、優缺點

MVCC 使大多數讀操做均可以不用加鎖,這樣設計使得讀數據操做很簡單,性能很好,而且也能保證只會讀取到符合標準的行。不足之處是每行記錄都須要額外的存儲空間,須要作更多的行檢查工做,以及一些額外的維護工做。

適用場景

1、悲觀鎖

  1. 用來解決讀-寫衝突和寫-寫衝突的的加鎖併發控制

  2. 適用於寫多讀少,寫衝突嚴重的狀況,由於悲觀鎖是在讀取數據的時候就加鎖的,讀多的場景會須要頻繁的加鎖和不少的的等待時間,而在寫衝突嚴重的狀況下使用悲觀鎖能夠保證數據的一致性

  3. 數據一致性要求高

  4. 能夠解決髒讀,幻讀,不可重複讀,第一類更新丟失,第二類更新丟失的問題

2、樂觀鎖

  1. 解決寫-寫衝突的無鎖併發控制

  2. 適用於讀多寫少,由於若是出現大量的寫操做,寫衝突的可能性就會增大,業務層須要不斷重試,這會大大下降系統性能

  3. 數據一致性要求不高,但要求很是高的響應速度

  4. 沒法解決髒讀,幻讀,不可重複讀,可是能夠解決更新丟失問題

3、MVCC

  1. 解決讀-寫衝突的無鎖併發控制

  2. 與上面二者結合,提高它們的讀性能

  3. 能夠解決髒讀,幻讀,不可重複讀等事務問題,更新丟失問題除外

本文由博客一文多發平臺 OpenWrite 發佈!

相關文章
相關標籤/搜索