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

相关文章
相关标签/搜索