MySQL高併發事務問題及解決方案

事務的概念

  1. 事務 能夠理解爲一個 獨立的工做單元, 在這個獨立的工做單元中, 有一組操做; 放在事務(獨立工做單元)中的多個操做, 要麼所有執行成功, 要麼所有執行失敗。
  2. 難免俗套, 這仍是經過最經典的銀行轉帳應用來解釋一下mysql

    • 假設有兩個角色 'Iron Man'(餘額500), 'Wolverine'(餘額15), 如今 Iron Man 經過該銀行應用給 Wolverine 轉帳100元, 那麼本次轉帳操做至少須要三個步驟:sql

      檢查`Iron Man`餘額`>=100`元
      從`Iron Man`餘額中`-100`元
      給`Wolverine`餘額`+100`元
    • 注意: 上面的三個步驟的操做必須打包在一個事務中, 從而能夠做爲一個 獨立的工做單元 來執行。在這個 獨立工做單元(即事務) 中的這三個操做, 只要有任何一個操做失敗, 則事務就總體就是失敗的, 那就必須回滾全部的步驟。
    • 假設第二步操做成功, 可是第三步操做失敗, 那麼整個事務也就應該是失敗的, 那就必須將第二步的操做也回滾。(到這裏咱們也看到了事務最基本的特性之一: 保證數據的一致性)
  3. 要知道, 在真實的高併發場景下, 事務須要作的事情其實不少不少, 由於高併發會出現不少意想不到的問題, 接下來會分析這些問題。

事務的ACID特性

在分析高併發事務的問題前, 咱們要先知道事務的幾個標準特性, 由於一個運行良好的事務處理系統必須具有這些標準特性, 並且這些問題的解決離不開事務的這幾個標準特性!!!數據庫

  1. Atomicity 原子性
    一個事務必須被視爲一個不可分割的最小工做單元, 整個事務中的全部操做要麼所有提交成功, 要麼所有失敗回滾。對於一個事務來講, 不能只成功執行其中的一部分操做, 這就是事務的原子性。
  2. Consistency 一致性
    雖然可數據表中的數據可能一直在變化, 可是事務的一致性特性會保證 數據庫老是從一個一致性的狀態 轉換到 另外一個一致性的狀態;segmentfault

    好比在以前的轉帳例子:併發

    轉帳前的一致性狀態是: 'Iron Man'(餘額500), 'Wolverine'(餘額15)
    轉帳成功後的一致性狀態是: 'Iron Man'(餘額400), 'Wolverine'(餘額115)
    轉帳若是失敗的話, 一致性的狀態應該回滾到轉帳前的狀態: 'Iron Man'(餘額500), 'Wolverine'(餘額15)
  3. Isolation 隔離性高併發

    • 一般來講, 一個事務所作的修改在最終提交之前, 對其餘事務是不可見的;
      好比在以前的轉帳例子中, 在執行完成第二步, 可是第三步還沒開始的時候, 此時有另外一個帳戶彙總的程序開始運行, 那麼這個程序所拿到的A帳戶餘額應該是沒有被 -100 的餘額纔對
    • 後面咱們還會詳細討論事務隔離性隔離級別, 到時候就知道這裏爲何說一般來講對其餘事務是不可見的; (也就是還有特例, 好比最低隔離級別 READ UNCOMMITTED, 對其餘事務的可見就形成了髒讀問題的出現)
    • 事務有四種隔離級別(從低到高: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE)
  4. Durability 持久性性能

    一旦事務被最終提交, 則在事務這個獨立單元中的全部操做所作的修改將會 `永久保存到數據庫中`; (這裏所說的`永久`應該能夠理解爲 被事務修改的數據 是真正存放到了表中, 而不是存放在了諸如臨時表之類的地方)

高併發事務的問題

在併發量比較大的時候, 很容易出現 多個事務同時進行 的狀況。假設有兩個事務正在同時進行, 值得注意的是: 它們二者之間是互相不知道對方的存在的, 各自都對自身所處的環境過度樂觀, 從而並無對本身所操做的數據作必定的保護處理, 因此最終致使了一些問題的出現;
接下來, 在分析高併發事務的問題時, 你可能已經瞭解了一些關於鎖的概念, 可是在分析這些問題的時候, 先不要帶入鎖的概念, 本小節只會列出問題, 並直接告訴你各個問題是使用事務隔離性的哪一個隔離級別解決掉的, 鎖是解決方案, 若是帶入鎖的概念, 是沒法去分析這些問題的。因此本節不須要帶入!
[下一篇文章]()將會分析這些解決方案(各隔離級別)具體是如何解決問題的。spa

髒讀

  1. 若是mysql中一個事務A讀取了另外一個並行事務B未最終提交的寫數據, 那事務A的此次讀取就是髒讀。(由於事務A讀取的是'髒數據', 是'非持久性'的數據)code

    • 之因此說是'非持久性數據', '髒數據', 是由於事務B最終可能會由於內部其餘後續操做的失敗或者系統後續忽然崩潰等緣由, 致使事務最終總體提交失敗, 那麼事務A此時讀取到的數據在表中其實會被回滾, 那事務A拿到的天然就是髒的數據了。
    • 圖示:
      clipboard.png
  2. 事務A在T4階段讀取庫存爲20, 這個庫存其實就屬於髒數據, 由於事務B最終會回滾這個數據, 因此若是事務A使用庫存20進行後續的操做, 就會引起問題, 由於事務A拿到的數據已經和表中的真實數據不一致了。
  3. 那麼這個問題如何解決呢?
    在MySQL中, 其實事務已經用自身特性(隔離性的 -- READ COMMITED或以上隔離級別)解決了這個問題;blog

    **`READ COMMITED`級別保證了, 只要是當前語句執行前已經提交的數據都是可見的**。注意和`REPEATABLE READ`級別的區!!!

不可重複讀

  1. 假設如今上面的 髒讀問題 已經被徹底解決了, 那就意味着事務中每次讀取到的數據都是 持久性 的數據(被別的事務最終 提交/回滾 完成後的數據)。
  2. 可是你還須要知道的是: 解決了髒讀問題, 只是能保證你在事務中每次讀到的數據都是持久性的數據而已!!!!
  3. 若是在一個事務中屢次讀取同一個數據, 正好在兩次讀取之間, 另一個事務確實已經完成了對該數據的修改並提交, 那問題就來了: 可能會出現屢次讀取結果不一致的現象。
    clipboard.png
  4. 那麼這個問題如何解決呢?
    在MySQL中, 其實事務已經用自身特性(隔離性的 -- REPEATABLE READ或以上隔離級別)解決了這個問題;
    REPEATABLE READ級別保證了, 只要是當前事務執行前已經提交的數據都是可見的。注意和READ COMMITED級別的區!!!

幻讀 (間隙鎖)

對於幻讀, 可能不少人常常和不可重複讀區分不開, 詳情能夠參考本人寫的此篇文章https://segmentfault.com/a/11...

更新丟失

  1. 最後聊一下高併發事務的另外一個問題 -- 丟失更新問題, 該問題和以前幾個問題須要區分開, 由於解決方案不是一類!
  2. 第一類丟失更新: A事務撤銷時, 把已經提交的B事務的更新數據覆蓋了。
    clipboard.png
    不過, 經過後面MVCC相關文章最後的小結你會了解到, 這類更新丟失問題是不會出現的, 由於InnoDB存儲引擎的隔離級別都使用了排他鎖, 即便是 MVCC也不是純MVCC, 也用到了排他鎖! 這樣的話事務A在未完成的時候, 其餘事務是沒法對事務A涉及到的數據作修改並提交的。
  3. 第二類丟失更新: A事務覆蓋B事務已經提交的數據,形成B事務所作操做丟失。
    clipboard.png

    此類更新丟失問題, 沒法依靠前三種隔離級別來解決, 只能用最高隔離級別 Serializable 或者手動使用樂觀鎖, 悲觀鎖來解決。

  4. 最高隔離級別Serializable在實際應用場景中並不被採用, 對於手動使用樂觀鎖, 悲觀鎖的方案, 將會在之後關於鎖的文章中一併給出!

參考資料:

  • 《高性能MySQL》
相關文章
相關標籤/搜索