为什么时态表记录事务的开始时间?


8

更新临时表中的行时,该行的旧值存储在历史记录表中,事务开始时间为SysEndTime。当前表中的新值将使事务开始时间为SysStartTime

SysStartTimeSysEndTimedatetime2使用时态表来记录当行是当前版本列。事务开始时间是包含更新的事务开始的时间。

BOL说:

系统datetime2列中记录的时间基于事务本身的开始时间。例如,在单个事务中插入的所有行将在与SYSTEM_TIME周期开始相对应的列中记录相同的UTC时间。

示例:我开始从以下位置更新“订单”表中的所有行,20160707 11:00:00并且该事务需要5分钟才能运行。这将在历史记录表中为每行创建一行,并用SysEndTimeas 20160707 11:00:00。在当前表中的所有行会产生SysStartTime20160707 11:00:00

如果有人要20160707 11:01:00在更新运行时执行查询,他们将看到旧值(假设默认读取已提交隔离级别)。

但是,如果有人随后使用AS OF语法查询时态表,那么20160707 11:01:00他们将看到新值,因为它们SysStartTime将是20160707 11:00:00

对我来说,这意味着它不会显示当时的行。如果它使用事务结束时间,则该问题将不存在。

问题:这是设计使然吗?我想念什么吗?

我可以认为它正在使用事务开始时间的唯一原因是,它是事务开始时唯一的“已知”时间。它不知道事务在开始时将在何时结束,并且在结束时应用结束时间将花费一些时间,这将使正在应用的结束时间无效。这有意义吗?

应该允许您重新创建问题。


1
您回答了自己的问题,如果您使用事务结束时间,则在事务结束时还有另一个更新:更新完成20160707 11:04:58,现在您使用该时间戳更新所有行。但是此更新还会运行几秒钟,并在结束20160707 11:05:02,现在,哪个时间戳记是正确的事务结束时间?或假设您使用Read Uncommited20160707 11:05:00,并返回了行,但稍后AS OF不再显示它们。
dnoeth '16

@dnoeth是的,我想这个“问题”更多是对我的理论的澄清。
詹姆斯·安德森

我没有深入研究SQL Server的实现,但是Teradata多年来一直使用双时态表,因此我始终建议您阅读Richard Snodgrass(“发明”时态查询的人)的此案例研究,它基于Teradata的ANSI SQL之前的语法,但其概念是相同的:cs.ulb.ac.be/public/_media/teaching/infoh415/...
dnoeth

Answers:


4

这个想法是要跟踪逻辑时间与物理时间。逻辑只是指用户/应用程序期望插入/更新/删除的时间。DML操作可能出于某种原因可能要花费一些时间,这一事实没有意义,甚至用户也不容易确定和理解。如果您曾经不得不向会计师解释锁与闩锁的争用(我有),那是一个可比的情况。

例如,当鲍勃“告诉”鲍勃所在部门的所有雇员的应用开始在时20160707 11:00:00,每分钟42美元。鲍勃(及其雇员)期望从那时起每个人的报酬为每分钟42美元。Bob不在乎要实现此功能,该应用程序必须在每个员工上对数据库进行2次读取和6次写入,并且其数据+日志文件位于一堆RAID-5 SATA II驱动器上,因此大约需要7分钟为Bob的所有256名员工完成任务。鲍勃(Bob),他的会计师和薪资经理都在关心他的所有雇员起薪是$ 42 / min起20160707 11:00:00。否则,在更新的员工20160707 11:00:01会有些生气,而在更新记录的员工20160707 11:00:07会聚集在薪资部门之外。

有有效的用例来跟踪物理时间,例如调试和取证,但对于最终用户而言,通常是没有意义的。Tlog保留每个写入操作的排序和时序信息(以及其他信息),因此如果您知道外观,它就在那里。


好点。我猜该技术仅适用于您提到的某些用例。由于我在上面陈述的原因,似乎不太适合用于跟踪可能在非常短的时间内发生变化的价格或股票价值。
詹姆斯·安德森

其实不行 这是一个性能和规模问题。如果您需要保留股价的时间历史记录,则时间表仍然有效。您只需要确保插入片段非常细小并且可以在很小的窗口内完成即可。否则,后续更改将被阻止,并且如果传入速率足够高,则如果应用程序无法处理重试,则会发生超时并可能丢失数据。如果您运行数据库脱离融合IO或使用内存优化表,则可以轻松地每秒处理成千上万次插入操作,甚至每秒可以处理十万多个插入操作。
SQLmojoe

3

我相信这确实是一个设计缺陷,尽管它不是特定于SQL Server 2016的缺陷,因为所有其他现有的临时表实现(据我所知)都具有相同的缺陷。因此,时态表可能引起的问题非常严重。与通常可能出问题的地方相比,您的示例中的情况比较温和

损坏的外键引用:假设我们有两个临时表,表A的表对表B的外键引用。现在,我们有两个事务,它们都以READ COMMITTED隔离级别运行:事务1在事务2之前开始,事务2开始在表B中插入一行并提交,然后事务1在表A中插入对B的新添加行的引用。由于已经提交了向B添加新行的操作,因此满足了外键约束,并且事务1个能够成功提交。但是,如果要在事务1开始到事务2开始之间的某个时间查看数据库“ AS OF”,那么我们将看到表A带有对不存在的B行的引用。所以在这种情况下时间表提供了数据库的不一致视图。当然,这不是SQL:2011标准的意图,它指出,

系统版本表中的历史系统行形成了过去的不变快照。当该历史系统行是当前系统行时,创建该历史系统行时生效的所有约束都将被检查,因此永远不需要在历史系统行上实施约束。

非唯一主键:假设我们有一个包含主键和两个事务的表,两个表都处于READ COMMITTED隔离级别,其中发生以下情况:事务1开始之后但在触摸该表之前,事务2删除了一个表的行并提交。然后,事务1插入一个新行,该行具有与删除的主键相同的主键。这样做很好,但是当您在事务1开始到事务2开始之间的时间内查看表AS OF时,我们将看到具有相同主键的两行。

并发更新错误:假设我们有一个表和两个事务,它们都在READ COMMITTED隔离级别上更新了同一行。事务1首先开始,但是事务2首先更新该行。然后事务2提交,事务1然后​​对该行进行不同的更新并提交。一切都很好,除了如果这是一个临时表,在系统将历史记录表中插入所需行时,在事务1中执行更新时,生成的SysStartTime将是事务2的开始时间,而SysEndTime将是事务1的开始时间,这不是有效的时间间隔,因为SysEndTime早于SysStartTime。在这种情况下,SQL Server会引发错误并回滚事务(例如,请参见此讨论)。这是非常不愉快的,因为在READ COMMITTED隔离级别上,并不会期望并发问题会导致完全失败,这意味着应用程序不一定要准备进行重试。特别是,这违反了Microsoft文档中的“保证”:

这种行为保证了当您在将受益于版本控制的表上启用系统版本控制时,旧应用程序将继续运行。(链接

临时表的其他实现已通过提供一个选项(如果它们无效)自动“调整”时间戳来处理这种情况(两个并发事务更新同一行)(请参见此处此处)。这是一个丑陋的解决方法,因为不幸的是,它破坏了事务的原子性,因为同一事务中的其他语句通常不会以相同的方式调整其时间戳。即,通过这种解决方法,如果我们在某个时间点查看数据库“ AS OF”,那么我们可能会看到部分执行的事务。

:您已经建议了一个显而易见的解决方案,即实现使用事务结束时间(即提交时间)而不是开始时间。是的,的确如此,当我们在事务中间执行语句时,不可能知道提交时间是多少(因为将来是这样,或者如果要滚动事务则甚至不存在)背部)。但这并不意味着解决方案是不可行的;它只是必须以不同的方式完成。例如,当执行UPDATE或DELETE语句时,在创建历史记录行时,系统可以仅输入当前的事务ID,而不是开始时间,然后在提交事务后,系统稍后可以将ID转换为时间戳。 。

在这种实现的上下文中,我建议在提交事务之前,它添加到历史表的任何行都不应该是用户可见的。从用户的角度来看,应该只是在提交时添加了这些行(带有提交时间戳)。特别是,如果事务从未成功提交,那么它就永远不会出现在历史记录中。当然,这与SQL:2011标准不一致,后者描述了在UPDATE和DELETE语句时(而不是提交时)对历史记录(包括时间戳)的插入。但是,考虑到由于上述问题,该标准从未得到正确实施(可以说是不可能),因此我认为这并不重要。

从性能的角度来看,系统似乎不得不返回并重新访问历史记录行以填充提交时间戳,这似乎是不可取的。但是,取决于执行此操作的方式,成本可能会很低。我不太了解SQL Server的内部工作方式,但是例如PostgreSQL使用一个预写日志,因此,如果对表的同一部分执行多个更新,则将这些更新合并在一起,以便数据仅需写入一次物理表页面-通常在这种情况下适用。在任何情况下,

当然,由于(据我所知)这种系统从未实现过,所以我不能肯定地说它会起作用-也许我缺少了一些东西-但我看不出任何原因为什么它行不通。


0

在提交事务时,所有数据都必须写入数据页内(在内存中和日志文件中的磁盘上)。包括SysStartTimeSysEndTime列。您如何知道实际完成交易的结束时间?

除非您可以预测未来,否则使用事务开始时间是唯一的选择,即使它可能不太直观。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.