資料庫篇:mysql事務原理之MVCC檢視+鎖

潛行前行發表於2022-04-06

前言

  • 資料庫的事務特性
  • 資料併發讀寫時遇到的一致性問題
  • mysql事務的隔離級別
  • MVCC的實現原理
  • 鎖和隔離級別

關注公眾號,一起交流,微信搜一搜: 潛行前行

1 資料庫的事務特性

  • 原子性:同一個事務裡的操作是一個不可分割的,裡面的 sql 要麼一起執行,要不執行,是原子性
  • 隔離性:資料庫系統提供一定的隔離機制,保證事務在不受外部併發操作影響的“獨立”環境執行。這意味著事務處理過程中的中間狀態對外部是不可見的
  • 一致性:在事務開始和完成時,資料約束都必須保持一致狀態
  • 永續性:事務完成之後,它對於資料的修改是永久性的,即使出現系統崩潰也能夠保持持久

2 資料併發讀寫時遇到的一致性問題

  • 髒讀(針對未提交)
    • 兩個事務同時進行,事務A修改了資料D,且事務A未提交,而事務B卻可以讀取到未提交的資料D,稱之為髒讀
  • 髒寫
    • 兩個事務同時嘗試去更新某一條資料記錄時,當事務A更新時,事務A還沒提交,事務B就也過來進行更新,覆蓋了事務 A 提交的更新資料,這就是髒寫。一般要加鎖解決
  • 不可重複讀(針對已提交的 update)
    • 針對的是已經提交的事務修改的值,同時進行的其他事務給讀取到了,事務內多次查詢,多次讀到的是別的已經提交的事務修改過的值,這就導致不可重複讀
  • 幻讀(針對已提交的 insert)
    • 事務讀取到事務開始之後的插入資料,例如select * from table_user where id between 1 and 10,這條sql本應查出 1~9 的資料,id=10 此時不存在,之後其他事務再插入了一條 id=10 的記錄。然後當前事務再次查詢則會查出 10 條記錄。這就是幻讀
    • 和不可重複讀的區別是,不可重複讀的問題是讀取最新的修改,幻讀是讀取到最新的插入資料

3 mysql事務的隔離級別

  • 讀未提交(READ UNCOMITTED,RU):對應髒讀,可以讀取到最新未提交的修改
  • 讀已提交(READ COMMITTED,RC):一個事務能讀取另一個事務已經提交的修改。其避免了髒讀,但仍然存在不可重複讀和幻讀問題
  • 可重複讀(REPEATABLE READ,RR):同一個事務中多次讀取相同的資料返回的結果是一樣的。其避免了髒讀和不可重複讀問題,但幻讀依然存在
  • 序列化讀(SERIALIZABLE):事務序列執行。避免了以上所有問題

4 MVCC 的實現原理

MVCC 全稱Multi-Version Concurrency Control,其好處是讀不加鎖,讀寫不衝突,併發效能好

MVCC 的 undo log 版本鏈

  • InnoDB中每行資料都有隱藏列,隱藏列中包含了本行資料的事務ID trx_id、指向 undo log 的 roll_pointer 指標
    image.png
  • 基於undo log的版本鏈:前面說到每行資料的隱藏列中包含了指向 undo log 的指標 roll_pointer,而每條undo log 也會指向更早版本的undo log,從而形成一條版本鏈
    image.png

readView

對於使用READ COMMITTEDREPEATABLE READ隔離級別的事務來說,都必須保證讀到已提交事務修改過的記錄,也就是說假如另一個事務修改了記錄但尚未提交,是不能讀取最新版本的記錄的,其核心問題:需要判斷 MVCC 版本鏈中的哪個版本是當前事務可見的。innodb 的解決方案 readView,readView 包含4個比較重要的屬性

  • m_ids:在生成ReadView時,當前系統中活躍的讀寫事務 id 列表
  • min_trx_id:表示在生成ReadView時,當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中的最小值
  • max_trx_id:表示生成ReadView時系統中應該分配給下一個事務的 id 值
  • creator_trx_id:對應生成該ReadView 事務的id
readView 的訪問步驟
  • 如果被訪問版本的trx_id屬性值與ReadView中的creator_trx_id值相同,表示當前事務在訪問它自己修改過的記錄,該版本可以被當前事務訪問。
  • 如果被訪問版本的trx_id屬性值小於ReadView中的min_trx_id值,表明生成該版本的事務在當前事務生成ReadView前已經提交,所以該版本可以被當前事務訪問。
  • 如果被訪問版本的trx_id屬性值在ReadViewmin_trx_idmax_trx_id之間,那就需要判斷一下trx_id屬性值是不是在m_ids列表中,如果在,說明建立ReadView時生成該版本的事務還是活躍的,該版本不可以被訪問;如果不在,說明建立ReadView時生成該版本的事務已經被提交,該版本可以被訪問
  • 如果被訪問版本的trx_id屬性值大於或等於ReadView中的max_trx_id值,表明生成該版本的事務在當前事務生成ReadView後才開啟,該版本不可被當前事務訪問。反之可見
  • 如果某個版本的資料對當前事務不可見的話,那就順著版本鏈找到下一個版本的資料(undo log)。如果最後一個版本都不可見的話,那麼就意味著該條記錄對該事務完全不可見

讀已提交和可重複讀利用 ReadView 實現

  • 快照讀:讀取的是快照版本,也就是歷史版本 readView 裡的資料 ,普通的 SELECT 就是快照讀
  • 當前讀:讀取的是最新版本,UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE 是當前讀,需要加鎖
  • READ UNCOMMITTED:直接讀取記錄的最新版本就好
  • READ COMMITTED:每次讀取資料前都生成一個ReadView
    • 針對當前讀,RC 隔離級別保證對讀取到的記錄加鎖 (記錄鎖),存在幻讀現象
  • REPEATABLE READ:在第一次讀取資料時生成一個ReadView
    • 針對當前讀,RR 隔離級別保證對讀取到的記錄加鎖 (記錄鎖),同時保證對讀取的範圍加鎖,新的滿足查詢條件的記錄不能夠插入 (間隙鎖),不存在幻讀現象
    • RR 從嚴格意義上並沒解決幻讀。如果事務一開始先 update 一條看不見的資料(前面沒有當前讀操作),再查詢,則會多查出這條記錄,此時也是發生了幻讀

5 鎖和隔離級別

  • RC、RR、SERIALIZABLE 級別的隔離,當前讀都會需要藉助鎖實現
  • MVCC 能實現多數情況避免幻讀,但不能完全避免幻讀的發生
  • RR 隔離級別需要先 select ... for update 加鎖進行當前讀操作,才能防止幻讀
  • 對於SERIALIZABLE隔離級別的事務來說,InnoDB規定使用加鎖的方式來訪問記錄

歡迎指正文中錯誤

參考文章

相關文章