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)審計數據改動,為報表和數據分析提供支持,洞察記錄的變化趨勢

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

3)一旦發生誤操作的情況下可以及時進行數據恢復

 

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

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

 

Temporal Table(歷史表)的條件?

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM