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误操作(人为或应用程序错误)
2. 工作原理
当你创建时态表或将普通表转换(Alter)为时态表时,有2个表将会被创建:
- System-Versioned Table:记录当前值的当前表
- History Table,其中包含为每行记录的所有先前值
原理很简单:添加“开始”和“结束时间”列以使每个记录具有有效期。
示例中,创建了一个表'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,并且两个表的字段都是一样的
3. 核心信息
- 创建Temporal Table很简单,只需使用DateTime2数据类型添加开始日期和结束日期。
- 选项“WITH(SYSTEM_VERSIONING = ON(HISTORY_TABLE = <历史表名称>))”验证时态表的用法并指定历史表的名称。
- 在SSMS中,临时表的名称附近有(System-Versioned)。
- 历史记录表位于对象资源管理器树中具有符号的表下方
- 历史表自动生成在时态表创建期间创建的聚簇索引。
二、 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的结论非常简单,您无法指定开始日期。
下面我们多插入一些值
2. 更新命令
update命令与普通更新一样,我们执行两次下面的命令
update [Animals] set number=20 where name='ccc'
从执行计划中可以看出来实际上是更新了Animals表,插入了AnimalsHistory表(由于执行了两次,插入了两条记录)
3. 删除命令
对于删除,该行将从表中删除,就像标准删除一样。当delete命令运行时,该值将存储在历史记录表中,并带有结束日期和时间。
delete from [Animals] where name='EEE'
三、 SELECT命令
- 1. 查询特定时间的值
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'
- 2. 一次指定多个特定时间(类似sql的in语法)
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')
- 3. 查指定时间范围内的值
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。
- 添加计算列
- 添加IDENTITY列
- 当历史记录表设置为DATA_COMPRESSION=PAGE或DATA_COMPRESSION=ROW(历史记录表的默认值)时,添加SPARSE列或将现有列更改为SPARSE。
- 添加COLUMN_SET
- 添加ROWGUIDCOL列或将现有列更改为ROWGUIDCOL
- 2. 给Animals表添加一列
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'
发现还可以
- 3. 删掉Animals表一列
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操作数据
- 4. truncate表
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 ;
可以看到只清了时态表数据而没有清历史表数据
五、 维护和元数据
1. 备份和还原
在备份之前,我使用DBCC CHECKDB运行数据库完整性检查。
检查会包括历史记录表。我没有看到任何选项只是从表中进行备份而不包括历史表。恢复过程与备份过程一样,可以恢复两个表。
2. Index Rebuild&Reorg
第一步是查看History表中的聚簇索引是否在碎片视图中。
为了确保我能够在历史表上看到我创建的所有索引,我创建一个新的非聚簇索引并重新运行查询。
下一步是验证我是否可以在历史记录表中重建或重新组织索引。它运行得非常好:我很抱歉我的样本,而我没有足够的碎片来完成测试。
3. 元数据SYS.TABLES
Sys.tables有3个新的临时表列:
- temporal_type,标准表为0,历史表为1,系统版本表为2
- temporal_type_desc,其中包含来自temporal_type列的3种类型的描述
- history_table_id是系统版本表的id。
列出SQL Server数据库中的时态和历史表
select
t.object_id,
t.temporal_type,
t.temporal_type_desc,
h.object_id,
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)
七、 将时态表转回普通表
- 1. 将 SYSTEM_VERSIONING 设置为 OFF
如果希望对时态表执行特定的维护操作或者不再需要版本控制的表,请停止系统版本控制。 完成此操作后,将获得两个独立的表:
l 具有时间段定义的当前表
l 作为常规表的历史记录表
2. 重要提示
- 设置SYSTEM_VERSIONING = OFF或删除SYSTEM_TIME时间段时不会出现数据丢失的情况。
- 设置SYSTEM_VERSIONING = OFF且不删除SYSTEM_TIME时间段时,系统将继续更新每个插入和更新操作的时间段列。在当前表中执行的删除操作是永久性的。
- 删除SYSTEM_TIME时间段以完全删除该时间段列。
- 设置SYSTEM_VERSIONING = OFF时,具有足够权限的所有用户都能够修改历史记录表的架构和内容,甚至还可以永久删除该历史记录表。
3. 永久删除 SYSTEM_VERSIONING
此示例永久删除了 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;
4. 暂时删除 SYSTEM_VERSIONING
以下是需要将系统版本控制设置为 OFF的操作列表:
- 从历史记录中删除不必要的数据(DELETE 或 TRUNCATE)
- 从没有版本控制的当前表中删除数据(DELETE、 TRUNCATE)
- 从当前表对 SWITCH OUT 进行分区
- 将 SWITCH IN 分区到历史记录表
最后,时态表的缺点。它加大了维护负担,历史表保存每一个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