做为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
Manage Retention of Historical Data in System-Versioned Temporal Tables