我相信这确实是一个设计缺陷,尽管它不是特定于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使用一个预写日志,因此,如果对表的同一部分执行多个更新,则将这些更新合并在一起,以便数据仅需写入一次物理表页面-通常在这种情况下适用。在任何情况下,
当然,由于(据我所知)这种系统从未实现过,所以我不能肯定地说它会起作用-也许我缺少了一些东西-但我看不出任何原因为什么它行不通。
20160707 11:04:58
,现在您使用该时间戳更新所有行。但是此更新还会运行几秒钟,并在结束20160707 11:05:02
,现在,哪个时间戳记是正确的事务结束时间?或假设您使用Read Uncommited
了20160707 11:05:00
,并返回了行,但稍后AS OF
不再显示它们。