PostgreSQL、oracle、mysql中MVCC機制詳解

1、MVCC介紹

1.1、事務隔離級別
MVCC,Multi-version Concurrency Control ,顧名思義指的是多版本併發控制。在介紹MVCC之前我們先來簡單瞭解下事務的隔離級別:

  • read uncommitted:髒讀,一個事務可以讀到另外一個事務未提交的數據,大多數關係型數據庫不支持。
  • read committed:提交讀,一個事務可以讀到其他事務已經提交的數據,大多數數據庫的缺省模式。
  • repeatable read:一個事務執行過程中訪問的數據是一致的,也就是一個事務中多次讀到的數據不會變化。
  • serializable:序列化,事務串行化執行,避免不一致。代價很大,OLTP系統中很少使用。

在大多數關係型數據庫中默認的事務隔離級別都是read committed(mysql中默認是repeatable read)。我們也可以根據業務場景的需要去選擇合適的隔離級別,例如在一個事務中,使用select for update來避免讀到不一致的數據,如果不是爲了排他性的鎖定,用RR事務隔離級別來代替可以獲得更好的性能。

關於事務隔離級別更多詳細介紹見:一文徹底讀懂PostgreSQL事務隔離級別

1.2、併發控制介紹
常見的併發控制有:

  • MVCC:Multi-version Concurrency Control (多版本併發控制)。
  • S2PL: Strict Two-Phase Locking(嚴格二階段鎖):讀寫互斥,保證串行。
  • OCC:Optimistic Concurrency
    Control(樂觀併發控制):和悲觀鎖不同,樂觀鎖在提交前不加鎖,提交時如果讀取時的數據被其他事務修改並提交,則回退事務。

簡單來說,在MVCC機制還沒有出現之前,數據庫中讀寫是互斥的,而通過MVCC機制使得讀寫不互斥。因此MVCC使關係型數據庫併發讀寫能力得到很大的提高,而大多數OLTP系統中的85%以上是讀操作。

2、oracle的MVCC實現方式

Oracle的多版本併發控制是基於塊級的,利用Oracle UNDO/回滾段機制。在回滾段中保存了某個數據被修改之前的前映像的數據。
當我們在數據中查詢之前版本的數據時,oracle是這樣做的:首先查詢的過程會在undo段中查找該數據塊的前映像後,然後把前映像和current塊合併形成了一個CR block,通過查詢cr block就可以滿足數據的一致性了。
在這裏插入圖片描述
正因爲數據的前映像通過在DB BUFFER中的CR BLOCK來實現,所以數據無論修改多少次,都不會對存儲數據的數據段產生負面的影響。而且一個CR BLOCK生成後,可以在緩衝區中較長時間內存在,供相關的事務使用。這個功能對於大併發的讀操作來說,是十分有用的,可以大大提高相關操作的性能。

3、mysql的MVCC實現方式

mysql innodb引擎的MVCC也是通過undo段來實現的,但是和oracle不同的是:mysql的多版本併發控制是基於記錄級的,mysql中通過undo來形成行的版本鏈。
在這裏插入圖片描述
上圖中的回滾指針(DB_ROLL_PTR)字段用來指寫入回滾段(rollback segment)的 undo log record (撤銷日誌記錄記錄)。
而其具體做法就是通過兩個隱含列來實現的:一個是db_trx_id,指出該行的事務ID,一個是db_roll_ptr,指出這條記錄的pre-image數據在UNDO中的地址。

4、PostgreSQL的MVCC實現方式

最後我們再來看看PostgreSQL的MVCC機制,和oracle還有mysql中不同的是,pg中沒有undo這一概念,pg中的多版本併發是通過在表中數據行的多個版本來實現的,例如在一張表中我們要更新一條記錄,pg並不是直接修改該數據,而是通過插入一條全新的數據,同時對老數據加以標識。
在pg中一個page頁結構大致如下圖所示:
在這裏插入圖片描述
對應的數據結構爲PageHeaderData:
在這裏插入圖片描述
而實現多版本是通過HeapTupleFields:
在這裏插入圖片描述
要想理解pg的多版本機制,首先要弄清楚幾個關鍵的字段:

  • t_xmin:插入該元組的事務的txid;
  • t_xmax:刪除或更新該元組的事務的txid,如果尚未刪除或更新該元組,則t_xmax設置爲0;
  • t_cid:命令ID(cid),這表示從0開始在當前事務中執行此命令之前已執行了多少個SQL命令;
  • t_ctid:指向自身或新元組的元組標識符(tid),當該元組被更新時,該元組的t_ctid指向新的元組,否則,t_ctid指向自身。

關於這些字段的詳細介紹見:PostgreSQL表的系統字段

我們可以通過一個簡單的例子來看看:

bill=# select xmin,xmax,ctid,* from t;    
 xmin | xmax | ctid  | id | info 
------+------+-------+----+------
  824 |    0 | (0,1) |  1 | a
(1 row)

更新該表中的數據:
可以看到新記錄的xmin被置爲823,ctid指向新的記錄。

bill=# update t set info = 'b' where id=1;
UPDATE 1
bill=# select xmin,xmax,ctid,* from t;    
 xmin | xmax | ctid  | id | info 
------+------+-------+----+------
  825 |    0 | (0,2) |  1 | b
(1 row)

5、總結

在這裏插入圖片描述
oracle和mysql都是通過undo來實現多版本併發控制,而oracle和mysql還有pg不同的是它的多版本併發控制是基於塊級的,而mysql和pg是記錄級別的,個人認爲這也是爲什麼mysql中和pg中很難實現oracle中rac的原因——頁級別的多版本記錄在不同節點之間同步的效率必然比塊級別差很多。
同時pg的多版本機制導致pg並不適合頻繁update的場景,可能會帶來表和索引的高水位大幅提升的問題(PostgreSQL中的“高水位” )。不過定期進行vacuum full等操作還是能夠避免大部分問題的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章