TDSQL 全時態數據庫系統--核心技術
本文大綱:
Design
全時態數據模型
研究動機
數據模型
數據模型示例
歷史態數據存儲
數據轉儲時機
存儲格式
存儲模式
轉儲效率
歷史態數據可見性判斷算法
Design數據庫
本節討論T-TDSQL的關鍵之處,即影響T-TDSQL架構的設計之處。一是新的數據模型—全時態數據模型,表達了T-TDSQL的雙時態語義,其中對於數據的事務時態,首次提出全態數據的概念,以刻畫數據的生命週期。二是對於新的數據模型,如何在基於關係模型的數據庫中實現存儲,全時態數據的存儲,使得具備全時態語義的數據有了計算的依據;本文提出的全時態數據模型的實現,以MySQL爲載體。第三是全態數據的讀取,關鍵是歷史態數據的可見性判斷算法的實現,文獻對此進行了詳細的描述,本文對核心算法介紹。安全
全時態數據模型數據結構
本文采用了基於關係數據模型而設計的雙時態數據模型。其與普通的關係數據模型主要的區別在於如下兩點,一是數據具備狀態屬性,二是數據具備時態屬性。具備這兩種屬性的數據模型,稱爲全時態數據模型。架構
數據模型併發
數據的狀態屬性,標識數據的生命週期軌跡。數據的生命週期分爲三個階段,每一個階段刻畫數據的不一樣狀態屬性,以標識數據的生命週期軌跡中所處的狀態。ide
當前態(Current State):數據項的最新版本的數據,是處於當前階段的數據。處於當前階段的數據的狀態,稱爲當前態。線程
歷史態(Historical state):數據項在歷史上的一個狀態,其值是舊值,不是當前值。處於歷史階段的數據的狀態,稱爲歷史態。一個數據項的歷史態,能夠有多個,反映了數據的狀態變遷的過程。處於歷史態的數據,只能被讀取不能再被修改或刪除。設計
這三個狀態,涵蓋了一個數據項的生命週期,合稱爲數據全態(full-state),或稱爲全態數據。在MVCC機制下,數據的三種狀態均存在;在非MVCC機制下,數據只存在歷史態和當前態。對象
當前態:MVCC或封鎖併發訪問控制機制下,事務提交後的數據的新值處於當前態。
歷史態:MVCC機制下,當前活躍事務列表中最小的事務以前的事務生成的數據,其狀態處於歷史態。在封鎖併發訪問控制機制下,事務提交後,提交前的數據的值變爲歷史態的值,即數據項的舊值處於歷史態。
數據的雙時態屬性,分別爲有效時間屬性、事務時間屬性。
有效時間屬性表示數據表示的對象在時間屬性上的狀況。如Kate中學起止時間是2000-09-01到2003-07-30,而大學起止時間是2003-09-01到2007-07-30,這裏的時間就是有效時間。
事務時間屬性表示數據的某個狀態的時間發生時刻。數據具備其時態屬性,即在什麼時候數據庫系統進行了什麼樣的操做。某項操做在數據庫系統內被封裝爲事務,而事務具備原子性。所以,咱們採用了事務標誌來標識一個數據的事務時態屬性。
從形式上看,有效時間屬性和事務時間屬性,在數據模型中用普通的用戶自定義字段進行表示,只是用特定的關鍵字加以描述,供數據庫引擎進行約束檢查和賦值。
可是,一個定義有或不定義有雙時態屬性的數據項,其生命週期中必定存在全態形態,只是其全態形態的造成是經過事務時間屬性和DML操做觸發的。
數據模型示例
例如:對於員工-薪水這一關係,咱們能夠創建對應的雙時態數據模型,如圖2所示。其中,有效時間中表達「至今有效」,用關鍵字NOW;事務時間中表達「還沒有更改」,用關鍵字UC,(Until Changed)。以後,數據經歷以下操做:
op1. 2011-02-01 00:00:00添加新帳戶(4, ‘Jimmy’, 100, 2010-01-01,NOW);
op2. 2012-01-01 00:00:00更新Kim的餘額爲200;
op3. 2013-01-01 00:00:00調整Kim的餘額爲300;
圖2中的數據通過上述操做,結果變遷爲圖3所示,圖3表示了上述操做完成後的時刻處於全時態的數據情況和變遷過程相關操做。
圖2 初始的雙時態數據模型圖(用戶表)
圖3變遷的雙時態關係模型圖(歷史表)
歷史態數據存儲
MySQL/InnoDB,PostgreSQL等採用MVCC技術的關係型數據庫,對於多版本的管理方案也不盡相同。MySQL/InnoDB將歷史態版本的數據經過Undo Log在內存中保存。PostgreSQL將歷史態版本元組直接連接在最新版本元組後,所以元組的多個版本在同一個數據頁面上(跨頁狀況存在)。MySQL/InnoDB經過Purge操做來對歷史態版本進行清理, PostgreSQL經過Vacuum來對歷史態數據進行清理。
主流數據庫(關係型和非關係型)不會保存歷史態數據,丟棄了有價值的歷史態數據。而歷史態數據的價值,能夠分爲五個方面,第五章針對全時態類應用價值進行了討論。
本節將基於MVCC技術,討論對歷史態數據進行存儲的方案。
數據轉儲時機
相對於只支持當前態數據獲取的數據庫系統而言(如Oracle、MySQL/InnoDB、PostgreSQL),對於歷史態數據的轉儲,須要考慮兩個問題:
什麼時候數據會被丟失而須要進行轉儲?
在歷史態數據被按期清理時,是將歷史狀態的數據進行轉儲的最佳時機,此時數據庫系統已經再也不須要對歷史態數據進行DML操做。
因爲系統清理是一種批量操做,因此歷史態數據也是採用相似的批量轉儲策略。當數據清理線程/進程工做時,轉儲線程/進程收集歷史態數據,插入到已經定義好的歷史表結構中。如圖4所示,給出了在MySQL/InnoDB系統中,一種可行且有效的數據轉儲方式。原表中被刪除或修改的歷史態版本會轉儲到歷史表中,並在歷史表中對數據進行從新組織,從而保證高的讀取效率。
在圖4中,咱們延用了3.1.2節中定義的例子,並多作一步操做op5.調整Kim的餘額爲400。從而展現了完成五步操做後全態數據的分佈狀況。元組「1,Kim,300」元組,假設還有併發事務在使用,所以爲過渡態。圖中歷史態數據的轉儲,將會在歷史態數據在UndoLog中被清除時發生。Undo Log中的一條元組(Undo Rec)元組了對應一條元組的歷史版本,Purge操做會將須要清理的Undo Log讀入內存中,咱們經過對Undo Rec的解析,將元組歷史版本從新以物理元組的形式組織起來,存入到歷史表中,從而作到歷史態數據的持久化存儲。
轉儲操做是一個原子操做,同時做爲一個內部事務執行,確保轉儲操做語義正確。未被轉儲的歷史態數據受系統舊有的故障恢復機制保護,確保不丟失。被轉儲後的歷史態數據被持久化存儲。
圖4 基於MySQL/InnoDB實現的歷史態數據轉儲原理圖
存儲格式
全時態數據模型,提供了全態語義和時態語義。
全態語義和時態語義對應的列信息,由用戶在CREATE TABLE語句中指定。而元組的結構,如圖5所示,包括兩部分,一是系統列,二是用戶定義列。系統列中的事務標識(Trx_id)表示本條版本是哪一個事務操做後產生的版本。全態語義和Trx_id客觀上表示了事務時態的語義,與表示有效時間的時態語義結合,使得全時態數據模型支持了雙時態時態數據庫的語義。
在用戶表上執行DML操做,須要爲歷史態版本的全態和時態對應列信息賦值。
歷史態的數據,存儲到歷史表。歷史表的結構和用戶原表的結構相近,只多一個列用於表示版本生成時對應的DML操做類型,值爲enum(Operation) = {更新,刪除,插入}={U,D,I }={3,2,1 }。
歷史表禁止DML 操做,保證歷史態數據的安全性。
從系統的角度看,歷史表中的數據,只容許進行脫機和聯機操做。
圖5 歷史表元組結構圖
存儲模式
根據用戶對歷史態數據的計算需求,在歷史表的定義中能夠指定的歷史態數據的存儲模式,當歷史態數據轉儲到歷史表中時,按照存儲模式,把歷史態數據轉儲爲行存格式或者列存格式。
行存格式與傳統的關係型數據庫沒有本質區別。
列存格式的數據,支持MySQL體系中Column Store數據格式。另外將支持Parquet、RCFile、ORCFile等列存格式。
轉儲效率
對於列存格式的存儲模式,提供內存式轉儲過渡區,用以緩衝行格式的待轉儲的歷史態數據。等到轉儲過渡區滿,利用壓縮技術從新組織行存格式爲列存。如圖6所示。
轉儲過渡區由若干個連續的內存BLOCK/PAGE組成,每一個BLOCK/PAGE大小等同於數據庫系統初始化階段指定的BLOCK/PAGE大小。
圖6 轉儲過渡區原理圖
同一個數據項可存在多個歷史態的版本。
哪一個歷史態的版本能夠被某個快照差讀取,是由歷史態數據可見性判斷算法決定的。此算法是一種新算法,有別於諸如PostgreSQL、MySQL/InnoDB中的版本可見性判斷算法。以前的算法能夠稱之爲當前態數據可見性判斷算法,能讀出全態數據中的當前態和過渡態數據。
歷史態數據可見性判斷算法與當前態數據可見性判斷算法這兩個算法合稱爲全態數據可見性判斷算法。
歷史態數據的可見性判斷,再也不可以依賴活躍事務鏈表,這是由於對於歷史上任什麼時候刻,其對應的「當前活躍事務列表」因時間流逝而不可以被獲取。因此歷史態數據的可見性判斷算法有別與當前態數據的可見性判斷算法。
圖7 歷史態版本可見性判斷示例圖
圖7給出了一個使用歷史態數據可見性判斷算法、利用歷史快照差讀,獲取歷史態數據的實例。S1和S2是兩個歷史快照,存儲了快照的建立時間和其餘相關信息。基於算法1 [1],便可判斷一條元組版本在給出快照差中的可見性,並給出產生本條歷史態元組的操做。算法1輸入爲兩個事務快照s_start和s_stop,以及一條歷史態的元組版本r_i,輸出爲當前元組版本的可見性opT,0表明不可見,1表明該版本是插入操做產生的, 2表明該版本是更新操做產生的,3表明該版本是刪除操做產生的。
算法1 歷史態數據可見性判斷算法
1 function HISTORY_VISIBILITY_JUDGEMENT (r_i, s_start, s_stop) 2 opT = 0 3 if s.start.createTime<r_i.commitTime<s_stop.createTime then 4 if r_i.isDelete then 5 opT = 3 6 else 7 if r_i.prev() then 8 opT = 1 9 else 10 opT = 2 11 else 12 opT = 0 13 end if 14 end function