Microsoft在SQL Server 2016中基于最新版本的SQL标准:SQL:2011创建了一个新的功能——时态表(系统版本控制临时表)。
第一部分 - 原理与创建
第二部分 - INSERT,UPDATE&DELETE
第三部分 - SELECT
第四部分 - DDL语句
第五部分 - 维护与元数据
第六部分 - 如何将现有表转换为时态表
首先说明SQL服务器和Oracle之间的一个误解,Temporal Tables实际上与Oracle 12c Release 1中的新功能:Temporal Validity而非闪回技术更相似。更多信息参考此处
闪回基于撤消或闪回日志文件,而不是直接基于表。
每个系统版本控制表都分配有一个历史记录表,但该表对用户完全透明,除非他们想要通过创建附加索引或选择不同的存储选项来优化工作负载性能或存储空间。
下图说明了使用系统版本控制临时表的典型工作流:
1. 为何使用temporal
1) 它能够使数据库“回到某个时间”,查看当时的数据情况
2) 类似DML审计(INSERT,UPDATE,DELETE或MERGE),因为跟踪了所有数据的变化。
3) 恢复一定时间内dml误操作(人为或应用程序错误)
当你创建时态表或将普通表转换(Alter)为时态表时,有2个表将会被创建:
原理很简单:添加“开始”和“结束时间”列以使每个记录具有有效期。
示例中,创建了一个表'Animals'来存储动物。
CREATE TABLE [dbo].[Animals]
(
[AnimalId] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](200) NOT NULL,
[Genus Species] [varchar](200) NOT NULL,
[Number] [int] NOT NULL,
CONSTRAINT [PK_Animals] PRIMARY KEY CLUSTERED ([AnimalId] ASC),
/*Temporal: Define the Period*/
[StartDate] [datetime2](0) GENERATED ALWAYS AS ROW START NOT NULL,
[EndDate] [datetime2](0) GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME([StartDate],[EndDate])
)
WITH (SYSTEM_VERSIONING=ON(HISTORY_TABLE = [dbo].[AnimalsHistory]))
运行查询,您可以看到在SSMS中刷新Tables文件夹时,表名附近有一个“(System-Versioned)”文本。
如果要更改现有表,则需要为“开始日期”和“结束日期”定义默认值,如下所示:
ALTER TABLE [dbo].[Animals]
ADD StartDate datetime2(0) GENERATED ALWAYS AS ROW START CONSTRAINT P_StartDateConstraint DEFAULT'2000.01.01',
EndDate datetime2(0) GENERATED ALWAYS AS ROW END CONSTRAINT P_EndDateConstraint DEFAULTCONVERT(DATETIME2, '9999-12-31 23:59:59'),
PERIOD FOR SYSTEM_TIME(StartDate,EndDate);
在我的表中,我使用选项定义了历史表:
WITH (SYSTEM_VERSIONING=ON(HISTORY_TABLE = [dbo].[AnimalsHistory]))
您可以注意到,此表创建添加了Clustered索引ix_AnimalsHistory,并且两个表的字段都是一样的
二、 INSERT,UPDATE&DELETE
1. INSERT命令
Insert就像一个'普通'插入
INSERT INTO dbo.[Animals]([Name],[Genus Species],[Number])
VALUES('African wild cat','Felis silvestris lybica',10)
GO
快速检查两个表,可以看到Animals表有新行而历史表AnimalsHistory是空的。
可以插入一行并指定开始日期吗?——不可以
为开始日期添加默认值并使用约束插入行是否可以?——结果表明插入的是当前日期和时间,而不是我的默认值
如果插入时直接指定用默认值呢?——还是不行
对INSERT的结论非常简单,您无法指定开始日期。
下面我们多插入一些值
update命令与普通更新一样,我们执行两次下面的命令
update [Animals] set number=20 where name='ccc'
从执行计划中可以看出来实际上是更新了Animals表,插入了AnimalsHistory表(由于执行了两次,插入了两条记录)
对于删除,该行将从表中删除,就像标准删除一样。当delete命令运行时,该值将存储在历史记录表中,并带有结束日期和时间。
delete from [Animals] where name='EEE'
FOR SYSTEM_TIME AS OF 'datetime'
select * from [Animals] FOR SYSTEM_TIME AS OF '2019-03-08 02:12:53' where name='ccc'
注意查的是原表而不是历史表,如果查历史表会遇到报错
select * from AnimalsHistory FOR SYSTEM_TIME AS OF '2019-03-08 02:12:53' where name='ccc'
FOR SYSTEM_TIME CONTAINED IN ('2019-03-08 02:12:53','2019-03-08 08:12:53')
select * from [Animals] FOR SYSTEM_TIME CONTAINED IN ('2019-03-08 02:12:53','2019-03-08 08:12:53')
FOR SYSTEM_TIME FROM '2019-03-08 02:12:53' TO '2019-03-08 08:12:53'
或者
FOR SYSTEM_TIME BETWEEN'2019-03-08 02:12:53' AND '2019-03-08 08:12:53'
select * from [Animals] FOR SYSTEM_TIME FROM '2019-03-08 02:12:53' TO '2019-03-08 08:12:53'
select * from [Animals] FOR SYSTEM_TIME BETWEEN'2019-03-08 02:12:53' AND '2019-03-08 08:12:53'
四、 DDL语句
1. 注意事项
1) 在ALTER TABLE操作过程中,系统持有这两个表的架构锁。
2) 对于系统版本控制的临时表,Online 选项 (WITH (ONLINE = ON) 对 ALTER TABLE ALTER COLUMN不起任何作用。 无论为 ONLINE 选项指定的值是什么,都不会 联机执行 ALTER 列。
3) 不能使用直接 ALTER 进行以下架构更改。 对于这些类型的更改,请设置 SYSTEM_VERSIONING = OFF。
alter table [Animals] add test varchar(100);
可以看到历史表也已添加
我们看此时还能不能查询ddl前指定时间的数据
select * from [Animals] FOR SYSTEM_TIME AS OF '2019-03-08 02:12:53' where name='ccc'
发现还可以
alter table [Animals] drop column number
我们看此时还能不能查询ddl前指定时间的数据
select * from [Animals] FOR SYSTEM_TIME AS OF '2019-03-08 02:12:53' where name='ccc'
还是可以,但是其实可以看到没有删除那列的数据了,因此时态表无法恢复误ddl操作数据
truncate table [Animals]
发现不支持
查看官方文档,需要先暂停SYSTEM_VERSIONING
BEGIN TRAN
ALTER TABLE [Animals] SET (SYSTEM_VERSIONING = OFF);
truncate table [Animals]
ALTER TABLE [Animals] SET
(
SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.AnimalsHistory)
);
COMMIT ;
可以看到只清了时态表数据而没有清历史表数据
在备份之前,我使用DBCC CHECKDB运行数据库完整性检查。
检查会包括历史记录表。我没有看到任何选项只是从表中进行备份而不包括历史表。恢复过程与备份过程一样,可以恢复两个表。
第一步是查看History表中的聚簇索引是否在碎片视图中。
为了确保我能够在历史表上看到我创建的所有索引,我创建一个新的非聚簇索引并重新运行查询。
下一步是验证我是否可以在历史记录表中重建或重新组织索引。它运行得非常好:我很抱歉我的样本,而我没有足够的碎片来完成测试。
Sys.tables有3个新的临时表列:
列出SQL Server数据库中的时态和历史表
select
t.object_id,
t.name,
t.temporal_type,
t.temporal_type_desc,
h.object_id,
h.name,
h.temporal_type,
h.temporal_type_desc
from sys.tables t
inner join sys.tables h on t.history_table_id = h.object_id;
4. sys.periods
5. OBJECTPROPERTYEX中的属性“TableTemporalType”
OBJECTPROPERTYE中的新属性TableTemporalType精确地表示sys.tables中的temporal_type类型
六、 如何将普通表转换为时态表
先建一个普通表
create table systemParameters (
ParameterId int identity(1,1) not null primary key nonclustered,
Description varchar(100),
Value nvarchar(400),
)
请注意,时态表(即系统版本源表)必须定义主键。否则,在将System_Versioning设置为ON或启用system_versioning时,SQL引擎将抛出以下错误。
系统版本化的临时表'master.dbo.systemParameters'必须定义主键。
创建一个具有完全相同列的表。当然,还要有两个新的附加列
create table systemParametersHistory (
ParameterId int not null,
Description varchar(100),
Value nvarchar(400),
SysStartTime datetime2 Not Null,
SysEndTime datetime2 Not Null
)
另请注意,主键不会在历史记录表中创建。历史表中的标识列也不允许这样做。
此时它们还是两个普通表
您决定手动创建历史表或让SQL Server 自动创建,我们添加两个datetime2字段,用于源时态表上的有效性开始和结束日期标识。另外添加缺少的时间段列,它是时态表的System_Time。
ALTER TABLE systemParameters
Add SysStartTime datetime2 Generated Always as Row Start Not Null,
SysEndTime datetime2 Generated Always as Row End Not Null,
Period for System_Time (SysStartTime, SysEndTime)
此时还是两个普通表
作为创建现有数据库表的历史表的最后一步,我们将在时态表上启用系统版本控制。时态表是包含当前数据的源表。
要为现有表启用系统版本控制,可以使用ALTER TABLE SET命令,并为system_versioning设置值ON。由于我们已经手动创建了历史表,因此我们必须使用history_table参数传递历史表名。
alter table systemParameters set (system_versioning = on (history_table = dbo.systemParametersHistory))
可以看到两张表已经变成了时态表和历史表
如果我们没有选择手动创建历史表,则表示数据库中没有名为systemParametersHistory的表。上面的命令将自动创建临时的系统版本表的历史表。
如果我们懒于创建历史表或在ALTER TABLE语句中传递历史表名,则以下命令也将起作用。不幸的是,在执行结束时,在我们的数据库中会有一个以“MSSQL_TemporalHistoryFor_ {object_id of temporal table}”格式命名的新历史表,
alter table test02 set (system_versioning = on)
七、 将时态表转回普通表
如果希望对时态表执行特定的维护操作或者不再需要版本控制的表,请停止系统版本控制。 完成此操作后,将获得两个独立的表:
l 具有时间段定义的当前表
l 作为常规表的历史记录表
此示例永久删除了 SYSTEM_VERSIONING 并完全删除了时间段列。删除时间段列是可选的操作。
ALTER TABLE dbo.Department SET (SYSTEM_VERSIONING = OFF);
/*Optionally, DROP PERIOD if you want to revert temporal table to a non-temporal*/
ALTER TABLE dbo.Department
DROP PERIOD FOR SYSTEM_TIME;
以下是需要将系统版本控制设置为 OFF的操作列表:
最后,时态表的缺点。它加大了维护负担,历史表保存每一个dml操作也加大了存储负载,另外它无法恢复误ddl操作。
https://blog.dbi-services.com/sql-server-2016-the-time-travel-with-temporal-table-part-i/
http://www.kodyaz.com/sql-server-2016/create-sql-server-2016-temporal-table.aspx
https://docs.microsoft.com/zh-cn/sql/relational-databases/tables/creating-a-system-versioned-temporal-table?view=sql-server-2017