您的位置:首页 > 数据库

SQL Server ->> 深入探讨SQL Server 2016新特性之 --- Temporal Table(历史表)

2016-02-22 09:40 417 查看
原文:SQLServer->>深入探讨SQLServer2016新特性之---TemporalTable(历史表)作为SQLServer2016(CTP3.x)的另一个新特性,TemporalTable(历史表)记录了表历史上任何时间点所有的数据改动。TemporalTable其实早在ANSISQL2011就提出了,而SAPHANA,DB2和Oracle早已在它们的产品中加入/实现了这一特性。所以说微软其实是落后了几个竞争对手。既然在CTP3.0中加入了,相信RTM也肯定有这个特性。

TemporalTable(历史表)有何作用?

1)审计数据改动,为报表和数据分析提供支持,洞察记录的变化趋势

2)实现了ETL中的SlowlyChangingDimension的类型2(保留所有数据的旧版本)

3)一旦发生误操作的情况下可以及时进行数据恢复

TemporalTable(历史表)和CDC的区别

以前微软为ETL提供了CDC功能来记录数据改动。TemporalTable同样是用于记录数据改动,但是它俩不一样。第一点,TemporalTable不像CDC是基于事务日志,它是作为事务的一部分被提交的。第二点,CDC是每次对新的表记录最新版本拷贝一份到另外一张表,而TemporalTable是把旧版本的记录转移到另外一张表,只有把TemporalTable和当前表的记录合并才可以构成表的整个历史版本(记录没有被删除的情况下)。

TemporalTable(历史表)的条件?

1)必须有主键;2)两个记录有效时间范围字段必须为notnull;3)历史表必须是和主表在结构上一模一样,包括字段名字和数据类型;

TemporalTable(历史表)如何实现?

TemporalTable其实对一对数据库表进行数据版本化(System-versioning)。一张是主表,一张是主表的历史记录表。TemporalTable的条件之一是添加两个类型为datetime2的字段来标示记录的有效时间范围--SysStartTime和SysEndTime。这两个字段是有系统自动更新的,可以选择在建表的时候对字段加入HIDDEN提示把字段隐藏,这样就避免在SELECT*FROM或者INSERTINTO的时候出现两个字段在列表里面。当插入(insert)发生时,事务开始的时间作为主表的SysStartTime,SysEndTime则被更新为9999-12-31,历史表不会有任何变化。当更新(update)发生时,历史记录表中的SysEndTime被更新为事务开始的时间,主表的SysStartTime则被更新为事务开始的时间,SysEndTime则被更新为9999-12-31。当删除(delete)发生时,历史记录表中的SysEndTime被更新为事务开始的时间。

查询TemporalTable(历史表)的记录

SQLServer对T-SQL提供了几个新的子句用于查询TemporalTable中的记录,即在正常的T-SQL查询语句后面添加新的子句:

FORSYSTEM_TIME
ALL,ASOF,BETWEEN...AND,FROM...TO,CONTAINEDIN


这里如果SysStartTime和SysEndTime相等时不会返回记录的。

ASOF<date_time>                    等于SysStartTime<=date_timeANDSysEndTime>date_time

FROM<start_date_time>TO<end_date_time>      等于SysStartTime<end_date_timeANDSysEndTime>start_date_time

BETWEEN<start_date_time>AND<end_date_time>  等于SysStartTime<=end_date_timeANDSysEndTime>start_date_time

CONTAINEDIN(<start_date_time>,<end_date_time>)等于SysStartTime>=start_date_timeANDSysEndTime<=end_date_time

ALL                             等于没有任何筛选条件

下图是来自MSDN的一张图,我觉得用于描述TemporalTable(历史表)的工作流程非常确切



TemporalTable(历史表)的最佳实践?

1)如果是做数据分析,比如统计一个平均值或者总数这样的分析,在History表的主键上建聚集列存储索引(clusteredcolumnstoreindex);

2)如果是做审计,对数据行有效时间范围字段建聚集索引,然后对主键也建立索引;

创建TemporalTable(历史表)

Historytable是不会出现在SQLServerManagementStudio的ObjectExplorer窗口的,但是你可以通过sys.tables找出来。

TemporalTable(历史表)可以有三种方法创建:1)你完全不关心名字,让SQLServer帮你创建包括帮你自动生成表名;2)你指定表名然后让SQLServer根据表名为你生成表结构;3)你事先创建好表;

由SQLServer自动创建History表

CREATETABLEdbo.TemporalTableTEST1
(
IDINTPRIMARYKEYCLUSTERED
,SysStartTimeDATETIME2GENERATEDALWAYSASROWSTARTNOTNULL
,SysEndTimeDATETIME2GENERATEDALWAYSASROWENDNOTNULL
,PERIODFORSYSTEM_TIME(SysStartTime,SysEndTime)
)
WITH(SYSTEM_VERSIONING=ON);


自己指定表名字和自己事先创建好表都是一样的语法。如果表已经存在,表的结构会被检查。检出出问题命令失败。

CREATETABLEdbo.TemporalTableTEST2
(
IDINTPRIMARYKEYCLUSTERED
,SysStartTimeDATETIME2GENERATEDALWAYSASROWSTARTNOTNULL
,SysEndTimeDATETIME2GENERATEDALWAYSASROWENDNOTNULL
,PERIODFORSYSTEM_TIME(SysStartTime,SysEndTime)
)
WITH(SYSTEM_VERSIONING=ON(HISTORY_TABLE=dbo.TemporalTableTEST2_History));


因为事先创建好的表可能已经存在数据行,建议添加好DATA_CONSISTENCY_CHECK来同时检查表中的数据行。建议在事先创建好表的情况下添加DATA_CONSISTENCY_CHECK选项

CREATETABLEdbo.TemporalTableTEST3
(
IDINTPRIMARYKEYCLUSTERED
,SysStartTimeDATETIME2GENERATEDALWAYSASROWSTARTNOTNULL
,SysEndTimeDATETIME2GENERATEDALWAYSASROWENDNOTNULL
,PERIODFORSYSTEM_TIME(SysStartTime,SysEndTime)
)
WITH(SYSTEM_VERSIONING=ON(HISTORY_TABLE=dbo.TemporalTableTEST3_History,DATA_CONSISTENCY_CHECK=ON));


如果是对一张现有表进行转换,要分两种情况:一种是表是空表,一种是表里面已经存在数据行。下面是对一张空表转换成TemporalTable的例子

--DROPTABLEdbo.TemporalTableTEST5

CREATETABLEdbo.TemporalTableTEST5
(
IDINTPRIMARYKEYCLUSTERED
)
GO

SELECT*FROMdbo.TemporalTableTEST5
GO

ALTERTABLEdbo.TemporalTableTEST5
ADDSysStartTimeDATETIME2GENERATEDALWAYSASROWSTART
CONSTRAINTDF_TemporalTableTEST5_SysStartDEFAULTSYSUTCDATETIME(),
SysEndTimeDATETIME2GENERATEDALWAYSASROWEND
CONSTRAINTDF_TemporalTableTEST5_SysEndDEFAULTCONVERT(datetime2(0),'9999-12-3123:59:59.9999999'),
PERIODFORSYSTEM_TIME(SysStartTime,SysEndTime)
GO

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


如果表中有数据,需要分开来完成这个转换过程。

--DROPTABLEdbo.TemporalTableTEST4

CREATETABLEdbo.TemporalTableTEST4
(
IDINTPRIMARYKEYCLUSTERED
)
GO

INSERTINTOdbo.TemporalTableTEST4(ID)
VALUES(1),(2),(3)
GO

--INSERTINTOdbo.TemporalTableTEST4(ID)
--VALUES(4),(5),(6)
--GO

ALTERTABLEdbo.TemporalTableTEST4
ADDSysStartTimeDATETIME2NOTNULLCONSTRAINTDF_TemporalTableTEST4_SysStartDEFAULTSYSUTCDATETIME(),
SysEndTimeDATETIME2NOTNULLCONSTRAINTDF_TemporalTableTEST4_SysEndDEFAULTCONVERT(DATETIME2,'9999-12-3123:59:59.9999999')
GO

SELECT*FROMdbo.TemporalTableTEST4
GO

--UPDATEdbo.TemporalTableTEST4SETSysEndTime='9999-12-3123:59:59.9999999'
--GO

--ALTERTABLEdbo.TemporalTableTEST4
--ALTERCOLUMNSysStartTimeDATETIME2GENERATEDALWAYSASROWSTART

--ALTERTABLEdbo.TemporalTableTEST4
--ALTERCOLUMNSysEndTimeDATETIME2GENERATEDALWAYSASROWEND

ALTERTABLEdbo.TemporalTableTEST4
ADDPERIODFORSYSTEM_TIME(SysStartTime,SysEndTime);
GO

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


如果你直接用第一种办法就会收到一个错误提示

Msg13575,Level16,State0,Line53
ADDPERIODFORSYSTEM_TIMEfailedbecausetable'JerryDB.dbo.TemporalTableTEST4'containsrecordswhereendofperiodisnotequaltoMAXdatetime.


为了证明SQLServer只要求主表和历史表的字段结构和约束一致,不要求分区和压缩选项一致,这里做一个实验

CREATEPARTITIONFUNCTIONmyPF(int)
ASRANGELEFTFORVALUES(1,100,1000);
GO
CREATEPARTITIONSCHEMEmyPS1
ASPARTITIONmyPF
TO([primary],[primary],[primary],[primary]);

--DROPTABLEdbo.TemporalTableTEST7_History

CREATETABLEdbo.TemporalTableTEST7_History
(
IDINTNOTNULL
,SysStartTimeDATETIME2NOTNULL
,SysEndTimeDATETIME2NOTNULL
)
ONmyPS1(ID)
WITH(DATA_COMPRESSION=PAGE)

CREATETABLEdbo.TemporalTableTEST7
(
IDINTPRIMARYKEYCLUSTERED
,SysStartTimeDATETIME2GENERATEDALWAYSASROWSTARTNOTNULL
,SysEndTimeDATETIME2GENERATEDALWAYSASROWENDNOTNULL
,PERIODFORSYSTEM_TIME(SysStartTime,SysEndTime)
)
WITH(SYSTEM_VERSIONING=ON(HISTORY_TABLE=dbo.TemporalTableTEST7_History));


上面是可行的。

总结一下:

总之记住几个点

1)PERIODFORSYSTEM_TIME(SysStartTime,SysEndTime)和SYSTEM_VERSIONING都是TemporalTable的特性,但是它俩在某些方面是不互相依赖。SYSTEM_VERSIONING是起到启动历史表的作用,PERIODFORSYSTEM_TIME是为标示表记录有效时间范围而存在,属于表结构属性的范畴。

2)PERIODFORSYSTEM_TIME隐式的将连个SYSTEMTIME的字段转换成ASROWSTARTS和ASROWENDS

3)SYSTEM_VERSIONING是不会阻止你去更新主键的,所以一旦你更新了主键,将会导致主表和历史表的记录错乱;

4)虽然PERIODFORSYSTEM_TIME(SysStartTime,SysEndTime)和SYSTEM_VERSIONING在某些方面不互相依赖,但是要更新SYSTEMTIME字段,需要SETSYSTEM_VERSIONING=OFF

对PARTITIONSWITCH的支持

条件是stage表需要有SYSTEMTIMEPERIOD,但是不要求表必须是SYSTEM_VERSIONING。这里对主表和历史表的限制是

主表:在SYSTEM_VERSIONING=ON的情况下SWITCHOUT是不允许的,我们都知道PARTITIONSWITCH仅是元数据(metadata)的变动,这样History表是捕捉不到分区内数据的,所以行不通;SWITCHIN在SYSTEM_VERSIONING=OFF的情况下是可以进行的,毕竟这样也不会对History表有什么影响,因为SWITCHIN相当于INSERT行为,INSERT对于History表没有影响。

历史表:SWITCHOUT可以在SYSTEM_VERSIONING=ON的情况下进行;SWITCHIN在SYSTEM_VERSIONING=ON的情况下则不行,因为这本身就违反了History表的数据验证流程;

注:

这里所说的DataConsistency检查只是检查是否SysStartTime<=SysEndTime

对TemporalTable的主表的结构改动

这一步SQLServer倒是做得挺好的,就是不需要你改完主表还要去改历史表。比如你添加一个字段和一个默认约束到主表,历史表也自动应用到同样的改动。

ALTERTABLEdbo.TemporalTableTEST4
ADDNameNVARCHAR(100)NOTNULLCONSTRAINTDK_TemporalTableTEST4_NameDEFAULT'Jerry'
GO


select*fromdbo.TemporalTableTEST4
select*fromdbo.TemporalTableTEST4_History



结果



某些情况下我们可以不停用SYSTEM_VERSIONING的情况下照样完成了对主表的结构改动,比如添加一个正常的字段(非compted等),但是如果添加诸如identity或者computed字段则需要停用system_versioning。否则

Msg13724,Level16,State1,Line135
System-versionedtableschemamodificationfailedbecauseaddingcomputedcolumnwhilesystem-versioningisONisnotsupported.


但是有一点是例外,就是如果你要删除主表的一个字段,除了要删除主表字段上创建的约束外还要删除历史表上对应字段的约束,否则

Msg5074,Level16,State1,Line142
Theobject'DF__TemporalTa__dttm__02FC7413'isdependentoncolumn'dttm'.
Msg4922,Level16,State9,Line142
ALTERTABLEDROPCOLUMNdttmfailedbecauseoneormoreobjectsaccessthiscolumn.


查询数据

上面讲了FORSYSTEM_TIME的五个子句ASOF|FROM...TO|BETWEEN...AND|CONTAINEDIN(<START>,<END>)|ALL

理解这几个其实很容易,完全无需去记住他们所应用的WHERE表达式。

ALL=全部嘛,这个不用讲

ASOF=ASOF的英文意思是自...开始,那就是某个时间点有效(包括这个时间点)的行

FROM...TO=时间区间内有效的行,但是不包含开闭的时间点,即不包含上限

BETWEEN...AND=时间区间内有效的行,包含开的时间点,即包含下限

CONTAINEDIN(<START>,<END>)=CONTAINED的意思是包含,也就是说记录有效区间处在我们指定的时间区间这个容器内

拿上面的TemporalTableTEST5来demo。先准备好数据。

ALTERTABLEdbo.TemporalTableTEST5
ADDfloat_colFLOAT
GO

INSERTINTOdbo.TemporalTableTEST5(ID,float_col)
VALUES(1,100),(2,200),(3,300)
GO

UPDATEdbo.TemporalTableTEST5SETfloat_col=float_col+50
WHEREID=1

UPDATEdbo.TemporalTableTEST5SETfloat_col=float_col+50
WHEREID=1

UPDATEdbo.TemporalTableTEST5SETfloat_col=float_col+50
WHEREID=1


查询全部的历史数据

SELECTID,float_col,[SysStartTime],[SysEndTime]
FROM[dbo].TemporalTableTEST5
FORSYSTEM_TIMEALL


IDfloat_colSysStartTimeSysEndTime
12502016-02-2109:27:52.03791979999-12-3123:59:59.9999999
22002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
33002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
11002016-02-2109:27:38.63630572016-02-2109:27:40.5162446
11502016-02-2109:27:40.51624462016-02-2109:27:46.4312559
12002016-02-2109:27:46.43125592016-02-2109:27:52.0379197


ASOF

SELECTID,float_col,[SysStartTime],[SysEndTime]
FROM[dbo].TemporalTableTEST5
FORSYSTEM_TIMEASOF'2016-02-2109:27:38.6363057';


IDfloat_colSysStartTimeSysEndTime
22002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
33002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
11002016-02-2109:27:38.63630572016-02-2109:27:40.5162446


FROM...TO

SELECTID,float_col,[SysStartTime],[SysEndTime]
FROM[dbo].TemporalTableTEST5
FORSYSTEM_TIMEFROM'2016-02-21'TO'2016-02-2109:27:38.6363057'

SELECTID,float_col,[SysStartTime],[SysEndTime]
FROM[dbo].TemporalTableTEST5
FORSYSTEM_TIMEFROM'2016-02-2109:27:40.5162446'TO'2016-02-22'


第一个没有记录返回

第二个返回了

IDfloat_colSysStartTimeSysEndTime
12502016-02-2109:27:52.03791979999-12-3123:59:59.9999999
22002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
33002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
11502016-02-2109:27:40.51624462016-02-2109:27:46.4312559
12002016-02-2109:27:46.43125592016-02-2109:27:52.0379197


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

SELECTID,float_col,[SysStartTime],[SysEndTime]
FROM[dbo].TemporalTableTEST5
FORSYSTEM_TIMEBETWEEN'2016-02-21'AND'2016-02-2109:27:38.6363057'

SELECTID,float_col,[SysStartTime],[SysEndTime]
FROM[dbo].TemporalTableTEST5
FORSYSTEM_TIMEBETWEEN'2016-02-2109:27:40.5162446'AND'2016-02-22'


第一个返回了

IDfloat_colSysStartTimeSysEndTime
22002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
33002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
11002016-02-2109:27:38.63630572016-02-2109:27:40.5162446


第二个返回了

IDfloat_colSysStartTimeSysEndTime
12502016-02-2109:27:52.03791979999-12-3123:59:59.9999999
22002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
33002016-02-2109:27:38.63630579999-12-3123:59:59.9999999
11502016-02-2109:27:40.51624462016-02-2109:27:46.4312559
12002016-02-2109:27:46.43125592016-02-2109:27:52.0379197


还是上面的例子改写,变成CONTAINEDIN

SELECTID,float_col,[SysStartTime],[SysEndTime]
FROM[dbo].TemporalTableTEST5
FORSYSTEM_TIMECONTAINEDIN('2016-02-21','2016-02-2109:27:38.6363057')

SELECTID,float_col,[SysStartTime],[SysEndTime]
FROM[dbo].TemporalTableTEST5
FORSYSTEM_TIMECONTAINEDIN('2016-02-2109:27:40.5162446','2016-02-22')


结果就是第一个没有返回任何结果

第二个返回了

IDfloat_colSysStartTimeSysEndTime
11502016-02-2109:27:40.51624462016-02-2109:27:46.4312559
12002016-02-2109:27:46.43125592016-02-2109:27:52.0379197


这个语法同样适用于UPDATE

讲了这么多,TemparalTable还是有需要方面可讲的,比如它对In-memoryOLTPOptimizedTable的支持啦,比如安全的考虑啦。真要将估计很多。姑且到这。今后有机会再深究下。最后,在这里思考下到底这个东西在现实生产环境中可以怎么好好利用或者结合其他的特性一起发挥它的最大价值呢?

1)首先我觉得基于上面讲到的可以作为数据误操作的数据复原。TemparalTable结合SQLServerAudit。TemparalTable实现记录历史记录改动,而SQLServerAudit提供了对用户行为的审计。两者通过时间来关联。这样我们就是当初这条旧的历史版本记录是被谁改动或者删除的。然后对SQLServerAudit加载的目标表创建聚集索引到时间行以及以时间字段创建分区表。再创建非聚集索引到Object字段,再结合PAGECOMPRESSION压缩SQLServerAudit加载的目标表的数据行。

2)报表分析这个案例我觉得视情况而定,要看到底是为了查看某条或者若干记录过去的变化趋势,还是查看数据分组后的平均变化情况或者是一些总量之类的东西。前者我觉得对TemparalTable的历史表应用聚集索引配合PAGECOMPRESSION,后者对TemparalTable的历史表创建ClusteredColumnstoreIndex,加上以分区表技术(时间字段选择endtime)。

参考:

TemporalTables
GettingStartedwithSystem-VersionedTemporalTables
TemporalTableSystemConsistencyChecks
PartitioningwithTemporalTables
TemporalTableConsiderationsandLimitations
ManageRetentionofDatainHistoryTablesinSystem-VersionedTemporalTables
System-VersionedTemporalTableswithMemory-OptimizedTables
TemporalTableMetadataViewsandFunctions

TemporalTableSecurity
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: