SQL Server ->> 深刻探討SQL Server 2016新特性之 --- Temporal Table(歷史表)

做爲SQL Server 2016(CTP3.x)的另外一個新特性,Temporal Table(歷史表)記錄了表歷史上任什麼時候間點全部的數據改動。Temporal Table其實早在ANSI SQL 2011就提出了,而SAP HANA, DB2和Oracle早已在它們的產品中加入/實現了這一特性。因此說微軟實際上是落後了幾個競爭對手。既然在CTP3.0中加入了,相信RTM也確定有這個特性。數據庫

 

Temporal Table(歷史表)有何做用?安全

1)審計數據改動,爲報表和數據分析提供支持,洞察記錄的變化趨勢ide

2)實現了ETL中的Slowly Changing Dimension的類型2(保留全部數據的舊版本)this

3)一旦發生誤操做的狀況下能夠及時進行數據恢復spa

 

Temporal Table(歷史表)和CDC的區別日誌

之前微軟爲ETL提供了CDC功能來記錄數據改動。Temporal Table一樣是用於記錄數據改動,可是它倆不同。第一點,Temporal Table不像CDC是基於事務日誌,它是做爲事務的一部分被提交的。第二點,CDC是每次對新的表記錄最新版本拷貝一份到另一張表,而Temporal Table是把舊版本的記錄轉移到另一張表,只有把Temporal Table和當前表的記錄合併才能夠構成表的整個歷史版本(記錄沒有被刪除的狀況下)。code

 

Temporal Table(歷史表)的條件?blog

1)必須有主鍵;2)兩個記錄有效時間範圍字段必須爲not null;3)歷史表必須是和主表在結構上如出一轍,包括字段名字和數據類型;索引

 

Temporal Table(歷史表)如何實現?事務

Temporal Table其實對一對數據庫表進行數據版本化(System-versioning)。一張是主表,一張是主錶的歷史記錄表。Temporal Table的條件之一是添加兩個類型爲datetime2的字段來標示記錄的有效時間範圍 -- SysStartTime和SysEndTime。這兩個字段是有系統自動更新的,能夠選擇在建表的時候對字段加入HIDDEN提示把字段隱藏,這樣就避免在SELECT * FROM或者INSERT INTO的時候出現兩個字段在列表裏面。當插入(insert)發生時,事務開始的時間做爲主表的SysStartTime,SysEndTime則被更新爲9999-12-31,歷史表不會有任何變化。當更新(update)發生時,歷史記錄表中的SysEndTime被更新爲事務開始的時間,主表的SysStartTime則被更新爲事務開始的時間,SysEndTime則被更新爲9999-12-31。當刪除(delete)發生時,歷史記錄表中的SysEndTime被更新爲事務開始的時間。

 

查詢Temporal Table(歷史表)的記錄

SQL Server對T-SQL提供了幾個新的子句用於查詢Temporal Table中的記錄,即在正常的T-SQL查詢語句後面添加新的子句:

FOR SYSTEM_TIME
ALL, AS OF, BETWEEN...AND, FROM...TO, CONTAINED IN

 

這裏若是SysStartTime和SysEndTime相等時不會返回記錄的。

AS OF <date_time>                     等於SysStartTime<= date_time AND SysEndTime> date_time

FROM <start_date_time> TO <end_date_time>      等於SysStartTime< end_date_time ANDSysEndTime> start_date_time

BETWEEN <start_date_time> AND <end_date_time>    等於SysStartTime<= end_date_time ANDSysEndTime> start_date_time

CONTAINED IN(<start_date_time> ,<end_date_time>)     等於SysStartTime>= start_date_time ANDSysEndTime<= end_date_time

ALL                             等於沒有任何篩選條件

 

下圖是來自MSDN的一張圖,我以爲用於描述Temporal Table(歷史表)的工做流程很是確切

 

Temporal Table(歷史表)的最佳實踐?

1)若是是作數據分析,好比統計一個平均值或者總數這樣的分析,在History表的主鍵上建彙集列存儲索引(clustered columnstore index);

2)若是是作審計,對數據行有效時間範圍字段建彙集索引,而後對主鍵也創建索引;

 

建立Temporal Table(歷史表)

History table是不會出如今SQL Server Management Studio的Object Explorer窗口的,可是你能夠經過sys.tables找出來。

 

Temporal Table(歷史表)能夠有三種方法建立:1)你徹底不關心名字,讓SQL Server幫你建立包括幫你自動生成表名;2)你指定表名而後讓SQL Server根據表名爲你生成表結構;3)你事先建立好表;

 

由SQL Server自動建立History表

CREATE TABLE dbo.TemporalTableTEST1
(
     ID INT PRIMARY KEY CLUSTERED
   , SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL
   , SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL
   , PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)   
)
WITH ( SYSTEM_VERSIONING = ON);

 

本身指定表名字和本身事先建立好表都是同樣的語法。若是表已經存在,表的結構會被檢查。檢出出問題命令失敗。

CREATE TABLE dbo.TemporalTableTEST2
(
     ID INT PRIMARY KEY CLUSTERED
   , SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL
   , SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL
   , PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)   
)
WITH ( SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.TemporalTableTEST2_History));

 

由於事先建立好的表可能已經存在數據行,建議添加好DATA_CONSISTENCY_CHECK來同時檢查表中的數據行。建議在事先建立好表的狀況下添加DATA_CONSISTENCY_CHECK選項

CREATE TABLE dbo.TemporalTableTEST3
(
     ID INT PRIMARY KEY CLUSTERED
   , SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL
   , SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL
   , PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)   
)
WITH ( SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.TemporalTableTEST3_History, DATA_CONSISTENCY_CHECK = ON));

 

若是是對一張現有表進行轉換,要分兩種狀況:一種是表是空表,一種是表裏面已經存在數據行。下面是對一張空錶轉換成Temporal Table的例子

--DROP TABLE dbo.TemporalTableTEST5

CREATE TABLE dbo.TemporalTableTEST5
(
     ID INT PRIMARY KEY CLUSTERED
)
GO

SELECT * FROM dbo.TemporalTableTEST5
GO

ALTER TABLE dbo.TemporalTableTEST5
ADD SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START
        CONSTRAINT DF_TemporalTableTEST5_SysStart DEFAULT SYSUTCDATETIME() ,
    SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END
        CONSTRAINT DF_TemporalTableTEST5_SysEnd DEFAULT CONVERT(datetime2 (0), '9999-12-31 23:59:59.9999999'),
    PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)
GO

ALTER TABLE dbo.TemporalTableTEST5
SET ( SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.TemporalTableTEST5_History, DATA_CONSISTENCY_CHECK = ON));
GO

 

 若是表中有數據,須要分開來完成這個轉換過程。

--DROP TABLE dbo.TemporalTableTEST4

CREATE TABLE dbo.TemporalTableTEST4
(
     ID INT PRIMARY KEY CLUSTERED
)
GO

INSERT INTO dbo.TemporalTableTEST4(ID)
VALUES(1),(2),(3)
GO

--INSERT INTO dbo.TemporalTableTEST4(ID)
--VALUES(4),(5),(6)
--GO

ALTER TABLE dbo.TemporalTableTEST4
ADD SysStartTime DATETIME2 NOT NULL CONSTRAINT DF_TemporalTableTEST4_SysStart DEFAULT SYSUTCDATETIME() ,
    SysEndTime DATETIME2 NOT NULL CONSTRAINT DF_TemporalTableTEST4_SysEnd DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999')
GO

SELECT * FROM dbo.TemporalTableTEST4
GO


--UPDATE dbo.TemporalTableTEST4 SET SysEndTime =  '9999-12-31 23:59:59.9999999'
--GO

--ALTER TABLE dbo.TemporalTableTEST4
--ALTER COLUMN SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START

--ALTER TABLE dbo.TemporalTableTEST4
--ALTER COLUMN SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END

ALTER TABLE dbo.TemporalTableTEST4
ADD PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime);
GO

ALTER TABLE dbo.TemporalTableTEST4
SET ( SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.TemporalTableTEST4_History, DATA_CONSISTENCY_CHECK = ON));
GO

 

若是你直接用第一種辦法就會收到一個錯誤提示

Msg 13575, Level 16, State 0, Line 53
ADD PERIOD FOR SYSTEM_TIME failed because table 'JerryDB.dbo.TemporalTableTEST4' contains records where end of period is not equal to MAX datetime.

 

爲了證實SQL Server只要求主表和歷史表的字段結構和約束一致,不要求分區和壓縮選項一致,這裏作一個實驗

CREATE PARTITION FUNCTION myPF (int)
AS RANGE LEFT FOR VALUES (1, 100, 1000);
GO
CREATE PARTITION SCHEME myPS1
AS PARTITION myPF
TO ( [primary], [primary], [primary], [primary] );

--DROP TABLE dbo.TemporalTableTEST7_History

CREATE TABLE dbo.TemporalTableTEST7_History
(
     ID INT NOT NULL
   , SysStartTime DATETIME2 NOT NULL
   , SysEndTime DATETIME2 NOT NULL
) 
ON myPS1(ID)
WITH (DATA_COMPRESSION = PAGE )


CREATE TABLE dbo.TemporalTableTEST7
(
     ID INT PRIMARY KEY CLUSTERED
   , SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL
   , SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL
   , PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)   
)
WITH ( SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.TemporalTableTEST7_History));

 

上面是可行的。

 

總結一下:

總之記住幾個點

1)PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)和SYSTEM_VERSIONING都是Temporal Table的特性,可是它倆在某些方面是不互相依賴。SYSTEM_VERSIONING是起到啓動歷史表的做用,PERIOD FOR SYSTEM_TIME是爲標示表記錄有效時間範圍而存在,屬於表結構屬性的範疇。

2)PERIOD FOR SYSTEM_TIME隱式的將連個SYSTEM TIME的字段轉換成AS ROW STARTS和AS ROW ENDS

3)SYSTEM_VERSIONING是不會阻止你去更新主鍵的,因此一旦你更新了主鍵,將會致使主表和歷史表的記錄錯亂;

4)雖然PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)和SYSTEM_VERSIONING在某些方面不互相依賴,可是要更新SYSTEMTIME字段,須要SET SYSTEM_VERSIONING = OFF

 

對PARTITION SWITCH的支持

條件是stage表須要有SYSTEMTIME PERIOD,可是不要求表必須是SYSTEM_VERSIONING。這裏對主表和歷史表的限制是

主表:在SYSTEM_VERSIONING=ON的狀況下SWITCH OUT是不容許的,咱們都知道PARTITION SWITCH僅是元數據(metadata)的變更,這樣History表是捕捉不到分區內數據的,因此行不通;SWITCH IN在SYSTEM_VERSIONING=OFF的狀況下是能夠進行的,畢竟這樣也不會對History表有什麼影響,由於SWITCH IN至關於INSERT行爲,INSERT對於History表沒有影響。

歷史表:SWITCH OUT能夠在SYSTEM_VERSIONING=ON的狀況下進行;SWITCH IN在SYSTEM_VERSIONING=ON的狀況下則不行,由於這自己就違反了History表的數據驗證流程;

 

注:

這裏所說的Data Consistency檢查只是檢查是否SysStartTime<=SysEndTime

 

對Temporal Table的主表的結構改動

這一步SQL Server卻是作得挺好的,就是不須要你改完主表還要去改歷史表。好比你添加一個字段和一個默認約束到主表,歷史表也自動應用到一樣的改動。

ALTER TABLE dbo.TemporalTableTEST4
ADD Name NVARCHAR(100) NOT NULL CONSTRAINT DK_TemporalTableTEST4_Name DEFAULT 'Jerry'
GO


select * from dbo.TemporalTableTEST4
select * from dbo.TemporalTableTEST4_History

 

結果

 

某些狀況下咱們能夠不停用SYSTEM_VERSIONING的狀況下照樣完成了對主表的結構改動,好比添加一個正常的字段(非compted等),可是若是添加諸如identity或者computed字段則須要停用system_versioning。不然

 

Msg 13724, Level 16, State 1, Line 135
System-versioned table schema modification failed because adding computed column while system-versioning is ON is not supported.

 

可是有一點是例外,就是若是你要刪除主表的一個字段,除了要刪除主表字段上建立的約束外還要刪除歷史表上對應字段的約束,不然

Msg 5074, Level 16, State 1, Line 142
The object 'DF__TemporalTa__dttm__02FC7413' is dependent on column 'dttm'.
Msg 4922, Level 16, State 9, Line 142
ALTER TABLE DROP COLUMN dttm failed because one or more objects access this column.

 

 查詢數據

上面講了FOR SYSTEM_TIME的五個子句 AS OF | FROM...TO | BETWEEN...AND | CONTAINED IN (<START>,<END>) | ALL

理解這幾個其實很容易,徹底無需去記住他們所應用的WHERE表達式。

ALL = 所有嘛,這個不用講

AS OF = AS OF的英文意思是自...開始,那就是某個時間點有效(包括這個時間點)的行

FROM... TO = 時間區間內有效的行,可是不包含開閉的時間點,即不包含上限

BETWEEN...AND = 時間區間內有效的行,包含開的時間點,即包含下限

CONTAINED IN (<START>,<END>) = CONTAINED的意思是包含,也就是說記錄有效區間處在咱們指定的時間區間這個容器內

 

拿上面的TemporalTableTEST5來demo。先準備好數據。

ALTER TABLE dbo.TemporalTableTEST5
ADD float_col FLOAT
GO

INSERT INTO dbo.TemporalTableTEST5(ID, float_col)
VALUES(1,100),(2,200),(3,300)
GO

UPDATE dbo.TemporalTableTEST5 SET float_col = float_col + 50
WHERE ID = 1

UPDATE dbo.TemporalTableTEST5 SET float_col = float_col + 50
WHERE ID = 1

UPDATE dbo.TemporalTableTEST5 SET float_col = float_col + 50
WHERE ID = 1

 

查詢所有的歷史數據

SELECT ID, float_col, [SysStartTime],[SysEndTime] 
FROM [dbo].TemporalTableTEST5 
FOR SYSTEM_TIME ALL
ID    float_col    SysStartTime    SysEndTime
1    250    2016-02-21 09:27:52.0379197    9999-12-31 23:59:59.9999999
2    200    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
3    300    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
1    100    2016-02-21 09:27:38.6363057    2016-02-21 09:27:40.5162446
1    150    2016-02-21 09:27:40.5162446    2016-02-21 09:27:46.4312559
1    200    2016-02-21 09:27:46.4312559    2016-02-21 09:27:52.0379197

 

AS OF 

SELECT ID, float_col, [SysStartTime],[SysEndTime] 
FROM [dbo].TemporalTableTEST5 
FOR SYSTEM_TIME AS OF '2016-02-21 09:27:38.6363057';
ID    float_col    SysStartTime    SysEndTime
2    200    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
3    300    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
1    100    2016-02-21 09:27:38.6363057    2016-02-21 09:27:40.5162446

 

FROM...TO

SELECT ID, float_col, [SysStartTime],[SysEndTime] 
FROM [dbo].TemporalTableTEST5 
FOR SYSTEM_TIME FROM '2016-02-21' TO '2016-02-21 09:27:38.6363057'

SELECT ID, float_col, [SysStartTime],[SysEndTime] 
FROM [dbo].TemporalTableTEST5 
FOR SYSTEM_TIME FROM '2016-02-21 09:27:40.5162446' TO '2016-02-22'

第一個沒有記錄返回

第二個返回了

ID    float_col    SysStartTime    SysEndTime
1    250    2016-02-21 09:27:52.0379197    9999-12-31 23:59:59.9999999
2    200    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
3    300    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
1    150    2016-02-21 09:27:40.5162446    2016-02-21 09:27:46.4312559
1    200    2016-02-21 09:27:46.4312559    2016-02-21 09:27:52.0379197

 

把FROM...TO的例子替換成BETWEEN...AND

SELECT ID, float_col, [SysStartTime],[SysEndTime] 
FROM [dbo].TemporalTableTEST5 
FOR SYSTEM_TIME BETWEEN '2016-02-21' AND '2016-02-21 09:27:38.6363057'

SELECT ID, float_col, [SysStartTime],[SysEndTime] 
FROM [dbo].TemporalTableTEST5 
FOR SYSTEM_TIME BETWEEN '2016-02-21 09:27:40.5162446' AND '2016-02-22'

第一個返回了

ID    float_col    SysStartTime    SysEndTime
2    200    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
3    300    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
1    100    2016-02-21 09:27:38.6363057    2016-02-21 09:27:40.5162446

第二個返回了

ID    float_col    SysStartTime    SysEndTime
1    250    2016-02-21 09:27:52.0379197    9999-12-31 23:59:59.9999999
2    200    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
3    300    2016-02-21 09:27:38.6363057    9999-12-31 23:59:59.9999999
1    150    2016-02-21 09:27:40.5162446    2016-02-21 09:27:46.4312559
1    200    2016-02-21 09:27:46.4312559    2016-02-21 09:27:52.0379197

 

仍是上面的例子改寫,變成CONTAINED IN

SELECT ID, float_col, [SysStartTime],[SysEndTime] 
FROM [dbo].TemporalTableTEST5 
FOR SYSTEM_TIME CONTAINED IN ('2016-02-21', '2016-02-21 09:27:38.6363057')

SELECT ID, float_col, [SysStartTime],[SysEndTime] 
FROM [dbo].TemporalTableTEST5 
FOR SYSTEM_TIME CONTAINED IN ('2016-02-21 09:27:40.5162446', '2016-02-22')

結果就是第一個沒有返回任何結果

第二個返回了

ID    float_col    SysStartTime    SysEndTime
1    150    2016-02-21 09:27:40.5162446    2016-02-21 09:27:46.4312559
1    200    2016-02-21 09:27:46.4312559    2016-02-21 09:27:52.0379197

 

這個語法一樣適用於UPDATE

 

講了這麼多,Temparal Table仍是有須要方面可講的,好比它對In-memory OLTP Optimized Table的支持啦,好比安全的考慮啦。真要將估計不少。姑且到這。從此有機會再深究下。最後,在這裏思考下到底這個東西在現實生產環境中能夠怎麼好好利用或者結合其餘的特性一塊兒發揮它的最大價值呢?

1)首先我以爲基於上面講到的能夠做爲數據誤操做的數據復原。Temparal Table 結合SQL Server Audit。Temparal Table實現記錄歷史記錄改動,而SQL Server Audit提供了對用戶行爲的審計。二者經過時間來關聯。這樣咱們就是當初這條舊的歷史版本記錄是被誰改動或者刪除的。而後對SQL Server Audit加載的目標表建立彙集索引到時間行以及以時間字段建立分區表。再建立非彙集索引到Object字段,再結合PAGE COMPRESSION壓縮SQL Server Audit加載的目標表的數據行。

2)報表分析這個案例我以爲視狀況而定,要看究竟是爲了查看某條或者若干記錄過去的變化趨勢,仍是查看數據分組後的平均變化狀況或者是一些總量之類的東西。前者我以爲對Temparal Table的歷史表應用匯集索引配合PAGE COMPRESSION,後者對Temparal Table的歷史表建立Clustered Columnstore Index,加上以分區表技術(時間字段選擇end time)。

 

參考:

Temporal Tables
Getting Started with System-Versioned Temporal Tables
Temporal Table System Consistency Checks
Partitioning with Temporal Tables
Temporal Table Considerations and Limitations
Manage Retention of Data in History Tables in System-Versioned Temporal Tables
System-Versioned Temporal Tables with Memory-Optimized Tables
Temporal Table Metadata Views and Functions

Temporal Table Security

Manage Retention of Historical Data in System-Versioned Temporal Tables

相關文章
相關標籤/搜索