文章目錄
- 前言
- MVCC
- Innodb的MVCC
- 版本鏈
- 回滾與提交
- 可見性判斷
- Oracle的MVCC
- 版本鏈
- PostgreSQL的MVCC
- MVCC實現
- 可見性判斷
- 特點
前言
?????MVCC(多版本并發控制,Multi-Version Concurrency Control)是一種數據庫管理系統(DBMS)中用于處理并發訪問的機制。它的核心思想是通過保存數據的多個版本來實現高效的讀寫并發操作,避免事務之間的阻塞,從而提高系統性能, 這篇文章將從幾個數據庫來分析
MVCC
? MVCC是為了在數據庫中, 讀寫不阻塞的情況下, 提供一致性讀的功能
??????在并發操作中,當正在寫時,如果有用戶在讀,這時寫可能只寫了一半,如一行的前半部分剛寫入,后半部分還沒有寫入,這時讀取到的數據行,可能是前半部分是新數據,后半部分是舊數據的不一致的行。解決這個問題的最簡單的辦法是 使用讀寫鎖,寫的時候,不允許讀,正在讀的時候也不允許寫,但這種方法導致讀和寫不能并發。于是,有人就想到了一種能夠讓讀寫并發的方法,這種方法就 是MVCC, MVCC的方法是寫數據時,舊的版本數據并不刪除,并發的讀還能讀到舊的版本數據,這樣就不會有問題了
?????從幾個經典的數據庫來看MVCC可以分為兩種, 一種是基于版本鏈的MVCC, 比如MySQL的Innodb存儲引擎, 還有Oracle數據庫, 這種是將舊數據放在回滾段, 通過一條記錄的回滾指針來找到上一個版本, 另一種是直接將舊版本的數據依然放在當前數據文件中, 比如PostgreSQL
Innodb的MVCC
? ?????對于一條記錄, 除開我們在創建表時規定的列, 還有幾個隱藏字段, 在MVCC我們需要關注這倆個
- trx_id : 創建這條記錄的事務號
- roll_pointer : 回滾指針, 指向上一個版本
版本鏈
通過回滾指針就可以形成版本鏈
- 在Innodb中存在purge線程,它會查詢那些比現在最老的活動事務還早的undo log,并刪除它們,從而保證undo log文件不至于無限增長。
回滾與提交
- 正常提交 : 一當事務正常提交時, innodb只需要更改事務狀態為commit即可,不需做其他額外的工作
- 回滾 : Rollback需要根據當前回滾指針從undo log中找出事務修改前的版本,并恢復。如果事務影響的行非常多,回滾則可能會很慢并且還會加行鎖, 阻塞其他事務,回滾時,也會產生redo日志
- Innodb的commit效率高,Rollback代價大
可見性判斷
? Innodb判斷一條記錄對于一個事務是否可見是通過讀視圖 ( Read View )來實現的
讀視圖可以看作一個數據結構, 里面有幾個重點字段
- Create_trx_id : 表示創建該讀視圖的事務id
- m_ids : 表示創建該讀視圖時, MySQL中活躍的事務id集合
- min_try_id : 表示創建該讀視圖時, MySQL中活躍的事務id最小值
- max_try_id : 表示創建該讀視圖時, MySQL中全局最大事務id+1
那么對于一條記錄
- 如果修改這條記錄的事務id比讀視圖中的min_id還小, 表示這條記錄是在創建該讀視圖之前就修改的, 那么對于當前事務這條數據是可見
- 如果這個id比讀視圖的Max_id還大, 說明是創建該讀視圖之后才修改的, 那么對于當前事務就是不可見的
- 如果在兩者之間, 再看讀視圖中的m_ids, 如果這個id存在讀視圖的m_ids里, 說明這條記錄還沒被提交, 那么對于當前事務不可見, 如果不在, 說明該記錄的事務已經被提交, 那么對于當前事務來說就可見
?????在Innodb的事務隔離級別中, 讀提交和可重復讀的區別就是創建讀視圖的時機不同, 讀提交是在該事務每次執行語句都會創建一個讀視圖, 那么在此過程中, 其他事務提交了, 那么就會影響新創建的讀視圖的min_try_id 和m_ids, 使得提交的數據對于當前事務可見, 而可重復讀是在事務啟動時創建一個讀視圖, 整個過程中都用這一個讀視圖, 在此期間, 其他事務提交了, 對于當前事務也是不可見的
Oracle的MVCC
? Oracle也是通過回滾段來實現MVCC的, 但oracle的實現更復雜,更精細一些。
Oracle相對MySQL的Innodb有幾個重點區別
- Oracle中也有事務ID, 但不是遞增的Undo Segment Number ( 回滾段 Number )+Transaction Table Slot Number ( 槽 Number ) +Wrap ( 槽重用次數), 所以不能通過事務id來判斷事務的執行前后
- 事務信息并不是記錄在每個數據行上的,而是在塊頭中的ITL槽上,所以相對來說更省空間
- ITL 槽存在數據塊頭部
- 每條記錄的頭部會有一個字節的數據表示使用了哪一個ITL槽 ( ITL 槽最大255 個)
- 因為Oracle數據庫的事務ID不能判斷事務的執行前后, 所以為了判斷事務的執行前后引入了一個叫SCN的單調遞增的序號
版本鏈
- Oracle的回滾信息是存在ITL槽中的, 每條記錄的頭部會有一個字節的數據表示使用了哪一個ITL槽, 通過這個ITL槽里的回滾指針, 就可以找到舊版本數據
- 在回滾段的數據會將舊的ITL槽和數據一起存, 這樣就形成了版本鏈
PostgreSQL的MVCC
??????PostgreSQL數據庫的MVCC不同于基于版本鏈的MVCC, 是沒有回滾段的, 舊數據是存放在原有數據文件中的
- 為了清理舊數據有一個垃圾回收操作vacuum來做這個事。 同時還有有自動垃圾回收autovacuum
- 更新操作中新行的物理位置發生了變化使用了HOT技術。如果原有的數據塊之間有空間,舊行與新行之間會建一個鏈接,索引上仍然指向舊的數據行。
MVCC實現
- 每行上有xmin和xmax兩個系統字段, 當插入一行數據時,將這行上的xmin設置為當前的事務id,而xmax設置為0
- 當更新一行時,實際上是插入新行,把舊行上的xmax設置為當前事務id,新插入行的xmin設置為當前事務id,新行的xmax設置為0
- 當刪除一行時,把當前行的xmax設置為當前事務id
- 當讀到一行時,到commitlog ( 記錄事務是提交還是回滾 ) 中查詢xmin和xmax對應的事務狀態是否是己提交還是回滾了,就能判斷出此行對當前行是否是可見。
可見性判斷
?????類似MySQL的innodb, PostgreSQL在執行一條SQL也會生成一個快照, 這里是SnapshotData的數據結構, 里面有幾個重點字段
- xmin : 己完成的的最大事務
- xmax : 未開始的事務
- xip : 當前數據庫中活動的事務id集合
那么對于一條數據
- 如果數據中的xmin>SnapshotData.xmax 說明這條數據是在當前事務之后的事務插入的, 那么對當前事務不可見
- 如果數據中的xmin在SnapshotData.xip中, 說明插入這條數據的事務還未提交, 那么對于當前事務不可見
- 如果數據中的xmin<SnapshotData.xmin, 說明插入這條數據的事務在當前事務之前, 那么就去commit log中查看該事務的狀態, 如果是回滾, 則不可見, 如果是提交, 那么接下來看xmax
- 如果xmax==0 || xmax in SnapshotData.xip 說明這條數據是當前事務執行前的最新版, 對當前事務可見
- 如果xmax<SnapshotData.xmin 說明可能存在更新的版本或該數據被刪除, 那么就去commit log中查看該xmax的事務的狀態, 如果是回滾, 那么對當前事務可見, 如果是提交, 說明有更新的版本或者被刪除, 那么這條就對當前事務不可見
特點
- 事務回滾可以立即完成,無論事務進行了多少操作, 數據可以進行很快更新
- 舊版本數據需要清理,有autovacuum進程vacuum命令處理