背景
这是一个与我正在使用的相似的示例:
CREATE TABLE sandboxTesting.TemporalTest (
GroupNumber VARCHAR(25) NOT NULL,
StartEffectiveWhen DATE NOT NULL,
EndEffectiveWhen DATE NULL,
ModifiedWhen DATETIME NULL,
IsReady BIT NOT NULL DEFAULT 0,
RowValidFrom DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL,
RowValidTo DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (RowValidFrom, RowValidTo),
CONSTRAINT PK_TemporalTest PRIMARY KEY CLUSTERED
(
GroupNumber, StartEffectiveWhen
)
) WITH (SYSTEM_VERSIONING=ON (HISTORY_TABLE=sandboxTesting.TemporalTestHistory))
GO
CREATE TRIGGER sandboxTesting.OnModify ON sandboxTesting.TemporalTest AFTER UPDATE AS
BEGIN
UPDATE temporalTst
SET temporalTst.IsReady = 0,
temporalTst.ModifiedWhen = GETDATE()
FROM sandboxTesting.TemporalTest temporalTst
JOIN deleted del
ON del.GroupNumber = temporalTst.GroupNumber
AND del.StartEffectiveWhen = temporalTst.StartEffectiveWhen
WHERE -- All business columns go here with OR statements in between them.
-- The idea is that if anything changes except the IsReady flag, then we
-- set the IsReady back to false. (IsReady has to be set by itself)
del.EndEffectiveWhen <> temporalTst.EndEffectiveWhen
OR (del.EndEffectiveWhen IS NULL AND temporalTst.EndEffectiveWhen IS NOT NULL)
OR (del.EndEffectiveWhen IS NOT NULL AND temporalTst.EndEffectiveWhen IS NULL)
END
GO
-- Insert new test
INSERT INTO [sandboxTesting].[TemporalTest] ([GroupNumber], [StartEffectiveWhen], [EndEffectiveWhen], [ModifiedWhen])
VALUES ('12345', '2024-01-1', NULL, NULL)
GO
-- Set is as ready
UPDATE sandboxTesting.TemporalTest
SET IsReady = 1
WHERE GroupNumber = '12345' AND StartEffectiveWhen = '2024-01-1'
GO
-- Change the End date
UPDATE sandboxTesting.TemporalTest
SET EndEffectiveWhen = '2024-09-02'
WHERE GroupNumber = '12345' AND StartEffectiveWhen = '2024-01-1'
-- Set the new end date as ready for billing.
UPDATE sandboxTesting.TemporalTest
SET IsReady = 1
WHERE GroupNumber = '12345' AND StartEffectiveWhen = '2024-01-1'
GO
-- Select the Data
SELECT * FROM sandboxTesting.TemporalTest for SYSTEM_TIME ALL
ORDER BY GroupNumber, StartEffectiveWhen desc, RowValidFrom DESC, RowValidTo DESC, ModifiedWhen desc
-- Select the Raw Data (for comparison)
SELECT * FROM sandboxTesting.TemporalTest
UNION ALL
SELECT * FROM sandboxTesting.TemporalTestHistory
ORDER BY GroupNumber, StartEffectiveWhen desc, RowValidFrom DESC, RowValidTo DESC, ModifiedWhen desc
当我运行它时,这是第一个结果:
群组号码 | 开始生效时间 | 生效日期 | 修改时间 | 已就绪 | 行有效地址 | RowValidTo |
---|---|---|---|---|---|---|
12345 | 2024-01-01 | 2024-09-02 | 2024-08-29 17:15:28.587 | 1 | 2024-08-29 23:15:28.5764223 | 9999-12-31 23:59:59.9999999 |
12345 | 2024-01-01 | 无效的 | 无效的 | 1 | 2024-08-29 23:15:28.5295658 | 2024-08-29 23:15:28.5764223 |
12345 | 2024-01-01 | 无效的 | 无效的 | 0 | 2024-08-29 23:15:28.4826980 | 2024-08-29 23:15:28.5295658 |
第二组输出如下:
群组号码 | 开始生效时间 | 生效日期 | 修改时间 | 已就绪 | 行有效地址 | RowValidTo |
---|---|---|---|---|---|---|
12345 | 2024-01-01 | 2024-09-02 | 2024-08-29 17:15:28.587 | 1 | 2024-08-29 23:15:28.5764223 | 9999-12-31 23:59:59.9999999 |
12345 | 2024-01-01 | 2024-09-02 | 2024-08-29 17:15:28.587 | 0 | 2024-08-29 23:15:28.5764223 | 2024-08-29 23:15:28.5764223 |
12345 | 2024-01-01 | 2024-09-02 | 无效的 | 1 | 2024-08-29 23:15:28.5764223 | 2024-08-29 23:15:28.5764223 |
12345 | 2024-01-01 | 无效的 | 无效的 | 1 | 2024-08-29 23:15:28.5295658 | 2024-08-29 23:15:28.5764223 |
12345 | 2024-01-01 | 无效的 | 无效的 | 0 | 2024-08-29 23:15:28.4826980 | 2024-08-29 23:15:28.5295658 |
这是不同的,因为第一个查询结果使用了for SYSTEM_TIME ALL
子句,而第二个查询结果只是查询原始数据。
不同之处在于,在第一个数据集中,第二个数据集的第二行和第三行已被过滤掉。它们被删除是因为第二行和第三行的开始日期和结束日期相同。(基本上说这些行从未真正生效。)
问题
我需要知道的是,对于通过触发器更新的数据,我是否可以依赖这个“零时间差” AFTER
?(如果不是这种情况,我需要编写一些查询,这些查询将会失败。)
我的意思是:如果我的服务器被数千个查询所困扰,这些查询都进行着大量的 IO 和计算,那么第二个数据集的第二行和第三行的RowValidFrom
和RowValidTo
值的差是否仍然为 0?
换句话说,这些值是否因为事务逻辑而相同?还是因为我的服务器速度很快并且目前没有任何压力,所以它们相同?
在您构建的场景中,不,第二行不能确定它是否由
AFTER
触发器更新。但第三行可以被视为触发触发器的操作。我在我的慢速笔记本电脑上运行了你的脚本,结果是这样的: 第二行的起始时间和结束时间不一样。但第三行的起始时间和结束时间相同,这是因为一行在
TemporalTest
同一个事务中被修改了多次。在你的示例中,阶段 1:更新EndEffectiveWhen
,这记录在第 3 行;阶段 2:AFTER TRIGGER
被触发以更新IsReady
,这记录在第 2 行。历史表标记了具有相同ValidFrom
和ValidTo
时间的记录,以识别更新是在同一事务中对同一 PK 执行的,并在最后一个历史记录中记录事务的结束时间。你可以在文档ValidTo
中查看描述。不会。只要您在同一事务中多次更新同一行(即使实际上没有更改任何列值),就会出现有效期为零的行。只有事务中的最终(有效)行更改才能具有非零有效期。
在您的例子中,触发器会进行最终更改,因此其有效期通常不为零。如果整个事务和后续更改在所用计时器的分辨率以下完成,则其长度仍为零。
请记住,触发器在触发语句的事务中运行。即使没有显式或隐式事务,也总会有由原始更新启动的自动提交事务。
当我将您的示例作为单个批处理运行时,关闭执行计划以尽量减少语句之间的延迟,我看到:
查询
ASOF
仅返回两行,因为语句完成得太快,三个历史记录条目的有效期为零。具体来说,更改结束日期、随后的触发器执行以及最终将IsReady设置为 1 似乎都是立即完成的。因此,视图
ASOF
看起来好像IsReady从未设置为零。您可以通过在触发器中引入小延迟或在 SSMS 中启用实际执行计划来避免测试中出现此类结果
WAITFOR
。无论如何,额外的延迟将防止非常快速的操作被记录为零有效期。通过执行计划,我看到:
延迟意味着
ASOF
查询现在显示IsReady从 0 到 1 的转换。您还可以一次运行一个测试语句,而不是一次性在单个批次中运行所有测试语句。
这些都不会改变这样一个事实:触发器所做的更改发生在触发语句的事务中,将是对该事务内特定行的最终更改,因此是唯一具有非零有效期的事件。