较旧值的临时表性能不佳


8

访问临时表中的历史记录时遇到一个奇怪的问题。通过AS OF子句访问时间表中较旧条目的查询所花的时间比对最近历史条目的查询所花的时间更长。

历史表是由SQL Server生成的(包括日期列上的聚簇索引并使用页面压缩),我向历史表中添加了5000万行,而我的查询检索了大约25,000行。

我试图确定问题的根本原因,但无法识别。到目前为止,我已经测试了:

  • 创建一个具有聚集索引的5000万行的测试表,以查看速度下降是否仅是由于数量引起的。我能够在固定时间(〜400ms)内检索到25K行。
  • 从历史表中删除页面压缩。这对检索时间没有影响,但是确实增加了表的大小。
  • 我尝试使用ID列和日期列直接访问历史记录表的行。这是事情变得更有趣的地方。我可以在〜400ms处访问表中的旧行,其中与AS OF子句一样,它需要1200ms的时间。我尝试对日期列上的测试表进行过滤,并且与对ID列进行过滤相比,发现了类似的减速。这使我相信日期比较是某些放缓的原因。

我想更多地看这个,但是我也想确保我没有吠错树。首先,在访问临时表中的较旧历史数据时,是否还有其他人遇到过相同的行为(我们仅注意到速度下降超过1000万行)?其次,我可以使用哪些策略来进一步隔离性能问题的根本原因(我刚刚开始研究执行计划,但对我来说仍然有点神秘)?

执行计划

这些是简单的检索查询:第一个访问较旧的行,第二个访问较新的行。

旧行〜1200ms执行时间

最近的行〜350ms执行时间

表格详情

这些是时间表中的列。历史记录表具有相同的列,但没有主键(根据历史记录表的要求): 时态表列

以下是历史记录表上的索引: 历史记录表上的索引

Answers:


6

赞恩对您的问题的评论中,他说:

...问题的一部分似乎是为了读取计划中的20K,您正在读取5000万行。

确实,这就是问题所在。没有索引可用于将部分或全部谓词下推到存储引擎。Microsoft在“文档”文章“时态表注意事项和限制 ”中为时态表推荐此基线索引策略:

最佳索引策略将包括当前表上的集群列存储索引和/或B树行存储索引,以及历史表上的集群列存储索引,以实现最佳的存储大小和性能。如果创建/使用自己的历史记录表,我们强烈建议您创建这种类型的索引,该索引由以期末列开头的期末列组成,以加快时间查询并加快数据一致性中的一部分查询校验。默认历史记录表具有基于期间列(结束,开始)为您创建的集群行存储索引。至少建议使用非集群行存储索引

措辞有点令人困惑(无论如何对我来说)。但要点是,您可以创建这些索引来提高性能,即使不是很多的话:

当前表的NC索引,以SysEndTime

CREATE NONCLUSTERED INDEX IX_SysEndTime_SysStartTime 
ON dbo.Benefits (SysEndTime, SysStartTime)
/*INCLUDE (ideally, include your other important fields here)*/;

这样,您可以通过寻找适当的结束时间来避免读取当前表中的某些行。

历史记录表上的CCI

CREATE CLUSTERED COLUMNSTORE INDEX ix_BenefitsHistory
ON dbo.BenefitsHistory
WITH (DROP_EXISTING = ON);

这将使您在历史记录表上获得批处理模式,这将使扫描更快。

当前表的NC索引,以SysStartTime

有关为何难以为日期范围查询建立索引的更多详细信息,请参阅Paul对问题“ 最有效的日期范围检索方法”的回答。根据那里的逻辑,在当前表上添加另一个以SysStartTime开头的NC索引是有意义的,以便优化器可以根据统计信息和查询的特定参数选择使用哪个NC索引:

CREATE NONCLUSTERED INDEX IX_SysStartTime_SysEndTime
ON dbo.Benefits (SysStartTime, SysEndTime)
/*INCLUDE (ideally, include your other important fields here)*/;

在我的测试案例中,创建上面概述的3个索引在资源使用方面产生了显着差异。我设置了一个测试用例,它运行两个查询,返回总行数为150万。历史记录和当前表均具有5000万行)。

注意:为减少SSMS开销,我在启用“执行后丢弃结果”选项的情况下运行了测试。

执行计划-默认索引

逻辑读取:1,330,612
CPU时间:00:00:14.718
经过的时间:00:00:06.198

执行计划-上面描述了索引

逻辑读取:27,656(8,111行存储+ 19,545列存储)
CPU时间:00:00:01.828
经过的时间:00:00:01.150

如您所见,所有3个度量均显着下降-包括总耗时,从6秒降至1秒。


Docs文章提供的另一种选择是放弃当前表上的两个NC索引,转而使用聚集的列存储索引。在我的测试中,性能与上述索引解决方案非常相似。


2

FOR SYSTEM TIME AS OF子句尝试返回指定时间的数据集。这意味着根据请求的系统时间,必须在内部回滚更新,必须“删除”删除的内容,并忽略插入。

过去的AS OF时间越远,需要验证的工作越多,以确保时态表在指定的系统时间就存在,因此查询将花费更长的时间。

如果数据表只是一个日志表,并且没有对数据进行任何更改,则使用记录的日期和索引将更快,更一致地返回数据。在这种情况下是否使用时间特征是不必要的。但是,如果对行(插入项除外)进行了更改,则使用时态表功能是返回所请求的确切数据(表在特定时间的状态)的唯一方法,您将只需接受时间查询的额外开销。

注意:“回滚”不是实际的回滚。临时表使用两个表-当前表和历史表。更改一行后,会将先前版本的副本以及该行有效的时间范围插入到“历史记录”表中。如果您在10/20/2018 10:20:20.18插入一行,在10/25/2018 10:25:20.18更新值,然后在12/01/2018 12:01:20.18再次更新该值,当前表中该行的最新版本,其开始日期为12/01/2018 12:01:20.18,历史表中的两行的有效范围为10/20至10/25/2018,以及10 / 25至12/01/2018


感谢您的回复!绝对有直觉的意义,但是我在阅读的文档中没有提到这种类型的行为(我只阅读了MS文档中时态表的基础知识)。您是否知道任何更详细描述该行为的文档?
Ebrahim Behbahani
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.