PostgreSQL、Oracle/MySQL和SQL Server的MVCC實現原理方式

studywell發表於2019-05-14

轉:


PostgreSQL、Oracle/MySQL和SQL Server的MVCC實現原理方式


關聯式資料庫管理系統使用MVCC(Multiversion Concurrency Control多版本併發控制)來避免寫操作堵塞讀操作的併發問題,MVCC也就是透過使用資料的多個版本保證併發讀寫不衝突的一種機制,不同的資料庫有不同的實現,這也是資料庫系統讓人頭疼的地方,關聯式資料庫表面看上去很簡單方便,使用標準的SQL語句操作讓人很放心,但是隨著系統規模增加,併發使用者增加,資料庫會出現效能降低的現象,這時我們可能需要從外部的微調進入到內部原理的深入研究,而每個資料庫內部實現併發的原理都是不同的,如果我們擁有多個不同的資料庫,那麼需要不同的調校方法,這時作為生產系統的核心資料庫開始變得不那麼讓人放心,本文提供了市面上幾種流行資料庫的內部MVCC不同的實現。

 
MVCC的兩種不同實現方式
 
第一種實現方式是將資料記錄的多個版本儲存在資料庫中,當這些不同版本資料不再需要時,垃圾收集器回收這些記錄。這個方式被PostgreSQL和Firebird/Interbase採用,SQL Server使用的類似機制,所不同的是舊版本資料不是儲存在資料庫中,而儲存在不同於主資料庫的另外一個資料庫tempdb中/
  第二種實現方式只在資料庫儲存最新版本的資料,但是會在使用undo時動態重構舊版本資料,這種方式被Oracle和MySQL/InnoDB使用。
 
下面看看具體資料庫實現機制。
 
PostgreSQL的MVCC
 
在PostgreSQL中,當一行記錄被更新時,該行資料的新版本(稱為tuple)將被建立並插入表中,之前版本提供一個指標指向新版本,之前版本被標記為"expired"過期,但是還保留在資料庫直到垃圾收集器回收掉。
 
為了支援多版本,每個tuple有以下附加資料記錄:
 
xmin – 插入更新記錄和建立這個tuple的事務的ID
 
xmax – 刪除記錄或建立這個tuple新版本或刪除記錄的事務。這個欄位初始是null.
 
事務狀態是儲存在 $Data/pg_clog的CLOG中. 這個表包含每個事務狀態資訊的兩個位元組,可能的狀態有in-progress, committed, 或  aborted。 當一個事務結束後,PostgreSQL並不會將資料庫記錄的改變undo回滾的,它只是在CLOG標記事務為aborted . 一個PostgreSQL表可能包含許多這樣aborted退出事務的資料。
 
一個稱為Vacuum 清理程式會提供expired過期/aborted退出的記錄版本的垃圾回收,Vacuum 清理器也會刪除被垃圾回收的tuple相關的索引項。
 
一個tuple的xmin是有效且xmax無效時,它是可見的。 “Valid有效” 意味著 “或者是 committed 或代表當前事務”. 為了避免反覆操作CLOG 表, PostgreSQL 在tuple中維持狀態標識,以表示tuple是否是“known committed” 或 “known aborted”.
 
Oracle的MVCC
 
Oracle是在回滾段(也就是‘undo log’)中儲存舊版本, 一個事務ID並不是一個順序數字,而是由一系列數字組成,這些數字指向回滾段的頭部事務槽 (slot)。 回滾段能讓新事務能重用儲存,重用被已經提交或退出的舊事務使用過的事務槽,這種自動重用機制使得Oracle使用有限的回滾段可以管理大量的事務。
 
回滾段的頭部塊是用作一個事務表,這裡儲存著事務的狀態,稱為System Change Number或 SCN, Oracle並不是儲存頁面中的每個記錄的事務ID, 而是透過儲存頁面中每行記錄的唯一事務ID的陣列陣列節約空間使用, 只儲存記錄的陣列偏移量offset,和每個事務ID儲存在一起的是一個指標,指向該頁事務建立的最後undo記錄,不僅表記錄是這種方式儲存,索引記錄也是使用同樣技術,這是Oracle和PostgreSQL主要區別之一.
 
當一個Oracle事務啟動時,它會標記一個當前事務狀態SCN. 當讀取一個表或一個索引頁時,Oracle使用SCN數字來決定該頁是否包含不應該讓當前事務知曉的事務影響效果, Oracle透過尋找相聯的回滾段頭部來檢查該事務的狀態,但是為了節約時間,第一次是真正查詢事務,查詢完成它的狀態會被記錄在該頁中以避免後來再次查詢,如果該頁被發現包含不可見事務的影響,Oracle透過undoing每個這樣的事務影響來重新建立該頁的舊版本。它掃描和每個事務有關的記錄,將這些事務效果應用到該頁,直至那些所有事務效果應用完成後被移除,以該方式建立的新頁再用於訪問其中的tuple。
 
Oracle中的記錄頭:
 
一個記錄頭部不會增長,總是有固定大小,對於非叢集的表,記錄頭部是3個位元組,一個位元組被用於儲存標識,一個位元組用於顯示記錄是否被鎖住(比如它被更新了但是沒有確認提交committed), ,一個位元組用於列計數。
 
SQL Server的MVCC
 
在SQL Server資料庫內部使用記錄版本實現快照隔離和讀取提交,只有需要此項的資料庫才會必須開啟並且會產生相應的成本開銷。
 
當一個記錄被修改或刪除時,使用copy-on-write機制能夠有效地啟動版本,Row versioning–based 事務能夠有效地“view看到” 資料的從過去到現在的的前後一致的各種版本。
 
記錄版本Row version儲存在版本儲存中,其駐留在主資料庫之外的tempdb資料庫中,  更特別地,當一張表或索引中一個記錄被修改,新記錄將攜帶上執行修改的事務的 ”sequence_number”. 記錄的舊版本將被複製到版本儲存中, 新記錄包含一個指標指向版本儲存中的這個舊記錄,如果多個長執行 long-running事務存在,並且需要多個 ”版本versions”, 在版本儲存中的記錄也許包含指向該記錄更早版本的指標。
 
SQL Server的版本儲存清除:
 
SQL Server自動管理版本儲存的大小,維持一個清除執行緒來確保版本儲存中記錄版本數量不至於太長,超過需要,對於在快照隔離下執行的查詢,版本儲存保留記錄版本直到修改資料的事務完成,並且事務包含的任何需要修改資料的語句全部完成,對於在Read Committed 快照隔離下執行的SELECT語句 ,一個特別的記錄版本就再也不需要了,一旦SELECT語句執行完成就被移除。
 
如果tempdb已經沒有空閒空間, SQL Server會呼叫清除功能,增加檔案的大小,當然前提是假設我們配置檔案是自動增長的,  如果磁碟已經沒有空間,檔案不能自動增長, SQL Server會停止產生版本,如果這種情況發生,任何需要讀取版本的快照查詢因為空間限制將失敗。
 
SQL Server中記錄頭
 
4 位元組
 
- 兩位元組記錄後設資料(記錄型別)
 
- 兩位元組向前指向記錄中的NULL 點陣圖bitmap. 這是記錄(固定長度列)實際大小的差值offset.
 
版本標記Versioning tag – 這是一個14-byte結構,包含時間戳加一個指向tempdb中版本儲存的指標,這裡時間戳是 trasaction_seq_number, 當需要支援一個版本操作時,加入版本資訊到記錄中時的時間。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29519108/viewspace-2644239/,如需轉載,請註明出處,否則將追究法律責任。

相關文章