Microsoft在SQL Server 2016中发布了一个新功能——时态表(系统版本控制临时表)。
一、 原理与创建
首先说明SQL服务器和Oracle之间的一个误解,Temporal Tables实际上与Oracle 12c Release 1中的新功能:Temporal Validity而非闪回技术更相似,更多信息参考此处。闪回基于undo或闪回日志文件,而不是直接基于表。
每个系统版本控制表都分配有一个历史记录表,但该表对用户完全透明,除非他们想要通过创建附加索引或选择不同的存储选项来优化工作负载性能或存储空间。
下图说明了使用系统版本控制临时表的典型工作流:
1. 为何使用temporal
- 能够使数据库“回到某个时间”,查看当时的数据情况,因此可以恢复一定时间内dml误操作
- 类似DML审计(INSERT,UPDATE,DELETE或MERGE),因为跟踪了所有数据的变化。
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]))
您可能注意到了,此表有聚集索引 ix_AnimalsHistory,并且两个表的字段都是一样的。
二、DML测试
1. INSERT
Insert与普通插入没有区别
INSERT INTO dbo.[Animals]([Name],[Genus Species],[Number]) VALUES('African wild cat','Felis silvestris lybica',10);
快速检查两个表,可以看到Animals表有新行而历史表AnimalsHistory是空的。
可以插入一行并指定开始日期吗?——不可以
为开始日期添加默认值并使用约束插入行是否可以?——结果表明插入的是当前日期和时间,而不是我的默认值
如果插入时直接指定用默认值呢?——还是不行
对INSERT的结论非常简单,您无法指定开始日期。
下面我们多插入一些值,为后续测试作准备。
2. update
我们执行两次下面的命令
update [Animals] set number=20 where name='ccc';
从执行计划中可以看出来实际上是更新了Animals表,插入了AnimalsHistory表(由于执行了两次,插入了两条记录)
3. delete
当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. 一次指定多个特定时间
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)对于系统版本控制的临时表,ALTER TABLE ALTER COLUMN 不支持 Online 操作。
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. 备份和还原
备份会包括原表和历史记录表(不可以选不备份),恢复过程与备份过程一样。
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.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
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引擎会报错。
创建一个具有完全相同列的表。当然,还要有两个新的附加列
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
如果希望对时态表执行特定的维护操作或者不再需要版本控制的表,请停止系统版本控制。
完成此操作后,将获得两个独立的表:
- 具有时间段定义的当前表
- 作为常规表的历史记录表
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操作。
参考