SQL Server 2016新功能--Temporal Table--快速恢复误操作

介绍SQL Server 2016 Temporal Table(历史记录表)功能,包含其工作原理、使用条件及如何创建和转换现有的表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Temporal Table SQL Server 2016的一个新特性,我们可以称之为历史记录表,它能记录表中任何时间点所做的数据改动。有了这个功能,就能够在发生误操作的情况下及时对数据进行恢复。


先简单介绍一下这个新特性,启用Temporal Table 功能后表结构如下图所示:



可以看到,启用Temporal Table 后,在主表中多了一张历史记录表,历史表中就记录了所有的数据改动。而且表中还多了2个字段,SysTimeStart SysTimeEnd,这2个字段类型都是datetime2,多出来的这2个字段记录了数据发生变动的时间。


向表写入数据时如下所示:



SysTimeStartSysTimeEnd 2个字段是由SQL Server自动写入的,记录了数据的操作时间,后面会详细介绍。可以看到,SysTimeStart的时间和写入数据的时间不一样,是因为SysTimeStartSysTimeEnd 所使用的是UTC格式的时间。UTC+时区=本地时间,所以SysTimeStart 的时间与本地时间要差8小时。

 

接下来我们详细介绍 Temporal Table功能

 

使用Temporal Table功能的条件:

1,必须要有主键。

2,必须定义两个数据类型为datetime2的列,用来记录开始和结束时间点,如上面图片中的SysTimeStart SysTimeEnd 。且字段不能为NULL

3,历史表必须和主表的结构一摸一样,包括字段名字和数据类型。



使用下面的语句创建有 Temporal Table 功能的表 

CREATE TABLE Test_TemporalTable(
	ID INT IDENTITY(1,1) PRIMARY KEY ,
	Names VARCHAR(10) , 
	SysTimeStart DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL ,
	SysTimeEnd DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL ,
	PERIOD FOR SYSTEM_TIME(SysTimeStart,SysTimeEnd)
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Test_TemporalTableHistroy)) ;


创建完成后,如下图所示:



历史记录表的列与主表的列完全一样,但所有的约束会被移除。历史记录表可以有自己的索引和统计信息,也可以在历史表上创建索引,提高历史记录表的性能

如果没有指定历史记录表的名称,SQL Server将自动生成一个dbo.MSSQL_TemporalHistoryFor_xxx的历史表,其中xxx是主表的object id,自动生成的表名效果如下图所示:



使用历史表功能


首先向主表中写入一些数据

INSERT INTO Test_TemporalTable (Names) VALUES ('Tom'),('Jakey'),('张三'),('李四')

写入数据后,我们查询主表和历史记录表,可以看到,历史记录表中并没有数据。且主表中SysTimeStart SysTimeEnd 由SQL Server自动填上数据,后面将会说明这两个字段的意义。



现在来修改和删除表中的数据

UPDATE Test_TemporalTable SET Names = '王五' WHERE ID = 1 

DELETE Test_TemporalTable WHERE ID = 2

再查询两张表,就会看到,历史记录表中已经把操作的数据记录了下来,并且记录下了操作的时间(Histroy表中的SysTimeEnd字段)


简单说明一下:

Temporal Table功能其实是对两张数据库表进行了数据版本化(System-versioning)。一张是主表,一张是主表的历史记录表。Temporal Table的条件之一是添加两个类型为datetime2的字段来标识记录数据的时间范围 ( SysTimeStart和SysTimeEnd)。这两个字段是由SQL Server自动进行维护的,可以在建表的时候对字段加入HIDDEN关键字把字段隐藏,这样就避免两个字段在SELECT * FROM或者INSERT INTO的时候出现在列表里面。

当写入(insert)时,写入的时间会被写入到主表的SysTimeStart字段中,SysTimeEnd 则被记录为'9999-12-31 23:59:59.9999999',历史记录表不会有任何记录。

当更新(update)发生时,历史记录表中的SysTimeStart会记录原数据写入的时间,SysTimeEnd会记录本次更新的时间,主表的SysTimeStart则被更新为本次更新的时间,SysEndTime依旧还是'9999-12-31 23:59:59.9999999'

当删除(delete)发生时,历史记录表中的SysTimeStart会记录原数据写入的时间,SysTimeEnd会记录本次删除操作的时间

如果一行数据再次发生Update操作,则在历史记录表中会再生产一行记录,将原表的值以及操作时间记录下来。



有了历史记录表中的数据,我们就能在发生误操作的时候,及时的将数据恢复,再不用还原备份才能恢复数据了,而且还可以根据时间来恢复数据。


有了这个新功能后,怎样将现有的表转换成Temporal Table的表呢?


要将现有表转换成Temporal Table功能的表,分2种情况,一种是现有的表中没有数据,是空表,一种是现有的表中已有数据,非空表。


下面来分情况转换


1,将空表转换成Temporal Table

/*创建一张空表,将空表转换成Temporal Table*/
CREATE TABLE dbo.Test_TemporalTable_New
(
    ID INT IDENTITY(1,1) PRIMARY KEY ,
    Names VARCHAR(10)
)
GO

SELECT * FROM dbo.Test_TemporalTable_New

GO

---将空表转换成Temporal Table
ALTER TABLE dbo.Test_TemporalTable_New
ADD SysTimeStart DATETIME2 GENERATED ALWAYS AS ROW START
CONSTRAINT DF_Test_TemporalTable_New_SysStart DEFAULT SYSUTCDATETIME() ,
SysTimeEnd DATETIME2 GENERATED ALWAYS AS ROW END
CONSTRAINT DF_Test_TemporalTable_New_SysEnd DEFAULT CONVERT(datetime2 (0), '9999-12-31 23:59:59.9999999'),
PERIOD FOR SYSTEM_TIME (SysTimeStart, SysTimeEnd)
GO

ALTER TABLE dbo.Test_TemporalTable_New
SET ( SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Test_TemporalTable_New_History, DATA_CONSISTENCY_CHECK = ON));
GO

代码中 DATA_CONSISTENCY_CHECK 只是检查 SysTimeStart 是否小于等于 SysTimeEnd


转换后的效果如下图所示:



2,将有数据的表转换成 Temporal Table 有数据的表转换比空表转换多出一个步骤。

/*将有数据的表转换成Temporal Table,一步一步做*/
ALTER TABLE dbo.Test_TemporalTable_Data
ADD SysTimeStart DATETIME2 NOT NULL  CONSTRAINT DF_Test_TemporalTable_Data_SysStart DEFAULT SYSUTCDATETIME() ,
    SysTimeEnd DATETIME2 NOT NULL CONSTRAINT DF_Test_TemporalTable_Data_SysEnd DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999')

GO

ALTER TABLE dbo.Test_TemporalTable_Data
ADD PERIOD FOR SYSTEM_TIME (SysTimeStart, SysTimeEnd);
GO

ALTER TABLE dbo.Test_TemporalTable_Data
SET ( SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Test_TemporalTable_Data_History, DATA_CONSISTENCY_CHECK = ON));
GO


转换后的效果如下图所示:














create table if not exists catalog_jp.ods_test.t_lifej_chdrpf_test1 ( chdrnum varchar(255) not null, cownpfx VARCHAR(255) NOT NULL, statcode varchar(255), cownnum VARCHAR(255) NOT NULL, proc_time timestamp(3), PRIMARY KEY (chdrnum, cownpfx, cownnum) NOT ENFORCED ) WITH ( 'path' = 'hdfs://lm1:8020/catalog_jp/ods_test.db/t_lifej_chdrpf_test1' ); insert into catalog_jp.ods_test.t_lifej_chdrpf_test1 select chdrnum, cownpfx, statcode, cownnum, PROCTIME() as proc_time from catalog_jp.ods.t_lifej_chdrpf a where a.chdrnum in ('5000000257', '5000000460', '5000001679', '9000000471', '9000014471', '9000016385', '400001683', '9000024744', '9000026178', '9000026861', '5090000782', '5090001363') and a.validflag='1'; --从表2 create table if not exists catalog_jp.ods_test.t_lifej_clntpf_test1 ( clntnum varchar(255) not null, kjaddr01 varchar(255) NOT NULL, kjaddr02 varchar(255), proc_time timestamp(3), PRIMARY KEY (clntnum) NOT ENFORCED ) WITH ( 'path' = 'hdfs://lm1:8020/catalog_jp/ods_test.db/t_lifej_clntpf_test1' ); --SET 'pipeline.name' = 'tableb'; insert into catalog_jp.ods_test.t_lifej_clntpf_test1 select clntnum, data_decrypt(kjaddr01, 'cbc', 'jp_dev') as kjaddr01, data_decrypt(kjaddr02, 'cbc', 'jp_dev') as kjaddr02, PROCTIME() as proc_time from catalog_jp.ods.t_lifej_clntpf d where d.clntnum in ('800315', '800336', '800454', '802356', '802413'); CREATE TABLE IF NOT EXISTS catalog_jp.dwd_test.tb_agrt_policy_curr_dim_test1( chdrnum VARCHAR(255) NOT NULL, statcode VARCHAR(255), cownnum VARCHAR(255), updateddt_a timestamp(3), currto VARCHAR(255), anbccd VARCHAR(255), updateddt_b timestamp(3), kjaddr01 VARCHAR(255), kjaddr02 VARCHAR(255), updateddt_c timestamp(3), PRIMARY KEY (chdrnum) NOT ENFORCED ) WITH ( 'connector' = 'paimon', 'merge-engine' = 'partial-update', 'fields.updateddt_a.sequence-group' = 'statcode,cownnum', 'fields.updateddt_b.sequence-group' = 'currto,anbccd', 'fields.updateddt_c.sequence-group' = 'kjaddr01,kjaddr02', 'partial-update.remove-record-on-sequence-group' = 'updateddt_a', 'changelog-producer' = 'lookup' ); INSERT INTO catalog_jp.dwd_test.tb_agrt_policy_curr_dim_test1 ( chdrnum, kjaddr01, kjaddr02, updateddt_c ) SELECT a.chdrnum, b.kjaddr01, b.kjaddr02, b.proc_time as updateddt_c FROM catalog_jp.ods_test.t_lifej_chdrpf_test1 a LEFT JOIN catalog_jp.ods_test.t_lifej_clntpf_test1 FOR SYSTEM_TIME AS OF a.proc_time b ON a.cownnum = b.clntnum; 分析可行性,是否有误?
最新发布
07-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值