PostgreSQL、Oracle/MySQL和SQL Server的MVCC實現(xiàn)原理方式
轉:
http://www.bkjia.com/oracle/1068936.html
PostgreSQL、Oracle/MySQL和SQL Server的MVCC實現(xiàn)原理方式
關系數(shù)據庫管理系統(tǒng)使用MVCC(Multiversion Concurrency Control多版本并發(fā)控制)來避免寫操作堵塞讀操作的并發(fā)問題,MVCC也就是通過使用數(shù)據的多個版本保證并發(fā)讀寫不沖突的一種機制,不同的數(shù)據庫有不同的實現(xiàn),這也是數(shù)據庫系統(tǒng)讓人頭疼的地方,關系數(shù)據庫表面看上去很簡單方便,使用標準的SQL語句操作讓人很放心,但是隨著系統(tǒng)規(guī)模增加,并發(fā)用戶增加,數(shù)據庫會出現(xiàn)性能降低的現(xiàn)象,這時我們可能需要從外部的微調進入到內部原理的深入研究,而每個數(shù)據庫內部實現(xiàn)并發(fā)的原理都是不同的,如果我們擁有多個不同的數(shù)據庫,那么需要不同的調校方法,這時作為生產系統(tǒng)的核心數(shù)據庫開始變得不那么讓人放心,本文提供了市面上幾種流行數(shù)據庫的內部MVCC不同的實現(xiàn)。
MVCC的兩種不同實現(xiàn)方式
第一種實現(xiàn)方式是將數(shù)據記錄的多個版本保存在數(shù)據庫中,當這些不同版本數(shù)據不再需要時,垃圾收集器回收這些記錄。這個方式被PostgreSQL和Firebird/Interbase采用,SQL Server使用的類似機制,所不同的是舊版本數(shù)據不是保存在數(shù)據庫中,而保存在不同于主數(shù)據庫的另外一個數(shù)據庫tempdb中/
第二種實現(xiàn)方式只在數(shù)據庫保存最新版本的數(shù)據,但是會在使用undo時動態(tài)重構舊版本數(shù)據,這種方式被Oracle和MySQL/InnoDB使用。
下面看看具體數(shù)據庫實現(xiàn)機制。
PostgreSQL的MVCC
在PostgreSQL中,當一行記錄被更新時,該行數(shù)據的新版本(稱為tuple)將被創(chuàng)建并插入表中,之前版本提供一個指針指向新版本,之前版本被標記為"expired"過期,但是還保留在數(shù)據庫直到垃圾收集器回收掉。
為了支持多版本,每個tuple有以下附加數(shù)據記錄:
xmin – 插入更新記錄和創(chuàng)建這個tuple的事務的ID
xmax – 刪除記錄或創(chuàng)建這個tuple新版本或刪除記錄的事務。這個字段初始是null.
事務狀態(tài)是保存在 $Data/pg_clog的CLOG中. 這個表包含每個事務狀態(tài)信息的兩個字節(jié),可能的狀態(tài)有in-progress, committed, 或 aborted。 當一個事務結束后,PostgreSQL并不會將數(shù)據庫記錄的改變undo回滾的,它只是在CLOG標記事務為aborted . 一個PostgreSQL表可能包含許多這樣aborted退出事務的數(shù)據。
一個稱為Vacuum 清理進程會提供expired過期/aborted退出的記錄版本的垃圾回收,Vacuum 清理器也會刪除被垃圾回收的tuple相關的索引項。
一個tuple的xmin是有效且xmax無效時,它是可見的。 “Valid有效” 意味著 “或者是 committed 或代表當前事務”. 為了避免反復操作CLOG 表, PostgreSQL 在tuple中維持狀態(tài)標識,以表示tuple是否是“known committed” 或 “known aborted”.
Oracle的MVCC
Oracle是在回滾段(也就是‘undo log’)中保存舊版本, 一個事務ID并不是一個順序數(shù)字,而是由一系列數(shù)字組成,這些數(shù)字指向回滾段的頭部事務槽 (slot)。 回滾段能讓新事務能重用存儲,重用被已經提交或退出的舊事務使用過的事務槽,這種自動重用機制使得Oracle使用有限的回滾段可以管理大量的事務。
回滾段的頭部塊是用作一個事務表,這里保存著事務的狀態(tài),稱為System Change Number或 SCN, Oracle并不是存儲頁面中的每個記錄的事務ID, 而是通過保存頁面中每行記錄的唯一事務ID的數(shù)組陣列節(jié)約空間使用, 只保存記錄的數(shù)組偏移量offset,和每個事務ID保存在一起的是一個指針,指向該頁事務創(chuàng)建的最后undo記錄,不僅表記錄是這種方式存儲,索引記錄也是使用同樣技術,這是Oracle和PostgreSQL主要區(qū)別之一.
當一個Oracle事務啟動時,它會標記一個當前事務狀態(tài)SCN. 當讀取一個表或一個索引頁時,Oracle使用SCN數(shù)字來決定該頁是否包含不應該讓當前事務知曉的事務影響效果, Oracle通過尋找相聯(lián)的回滾段頭部來檢查該事務的狀態(tài),但是為了節(jié)約時間,第一次是真正查詢事務,查詢完成它的狀態(tài)會被記錄在該頁中以避免后來再次查詢,如果該頁被發(fā)現(xiàn)包含不可見事務的影響,Oracle通過undoing每個這樣的事務影響來重新創(chuàng)建該頁的舊版本。它掃描和每個事務有關的記錄,將這些事務效果應用到該頁,直至那些所有事務效果應用完成后被移除,以該方式創(chuàng)建的新頁再用于訪問其中的tuple。
Oracle中的記錄頭:
一個記錄頭部不會增長,總是有固定大小,對于非集群的表,記錄頭部是3個字節(jié),一個字節(jié)被用于存儲標識,一個字節(jié)用于顯示記錄是否被鎖住(比如它被更新了但是沒有確認提交committed), ,一個字節(jié)用于列計數(shù)。
SQL Server的MVCC
在SQL Server數(shù)據庫內部使用記錄版本實現(xiàn)快照隔離和讀取提交,只有需要此項的數(shù)據庫才會必須開啟并且會產生相應的成本開銷。
當一個記錄被修改或刪除時,使用copy-on-write機制能夠有效地啟動版本,Row versioning–based 事務能夠有效地“view看到” 數(shù)據的從過去到現(xiàn)在的的前后一致的各種版本。
記錄版本Row version保存在版本存儲中,其駐留在主數(shù)據庫之外的tempdb數(shù)據庫中, 更特別地,當一張表或索引中一個記錄被修改,新記錄將攜帶上執(zhí)行修改的事務的 ”sequence_number”. 記錄的舊版本將被拷貝到版本存儲中, 新記錄包含一個指針指向版本存儲中的這個舊記錄,如果多個長運行 long-running事務存在,并且需要多個 ”版本versions”, 在版本存儲中的記錄也許包含指向該記錄更早版本的指針。
SQL Server的版本存儲清除:
SQL Server自動管理版本存儲的大小,維持一個清除線程來確保版本存儲中記錄版本數(shù)量不至于太長,超過需要,對于在快照隔離下運行的查詢,版本存儲保留記錄版本直到修改數(shù)據的事務完成,并且事務包含的任何需要修改數(shù)據的語句全部完成,對于在Read Committed 快照隔離下運行的SELECT語句 ,一個特別的記錄版本就再也不需要了,一旦SELECT語句執(zhí)行完成就被移除。
如果tempdb已經沒有空閑空間, SQL Server會調用清除功能,增加文件的大小,當然前提是假設我們配置文件是自動增長的, 如果磁盤已經沒有空間,文件不能自動增長, SQL Server會停止產生版本,如果這種情況發(fā)生,任何需要讀取版本的快照查詢因為空間限制將失敗。
SQL Server中記錄頭
4 字節(jié)
- 兩字節(jié)記錄元數(shù)據(記錄類型)
- 兩字節(jié)向前指向記錄中的NULL 位圖bitmap. 這是記錄(固定長度列)實際大小的差值offset.
版本標記Versioning tag – 這是一個14-byte結構,包含時間戳加一個指向tempdb中版本存儲的指針,這里時間戳是 trasaction_seq_number, 當需要支持一個版本操作時,加入版本信息到記錄中時的時間。