旧值的时态表性能不佳

Ebr*_*ani 10 performance sql-server temporal-tables

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

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

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

  • 创建一个包含 5000 万行并带有聚集索引的测试表,以查看速度变慢是否仅仅是由于数量所致。我能够在恒定时间(~400 毫秒)检索 25K 行。
  • 从历史表中删除页面压缩。这对检索时间没有影响,但确实显着增加了表的大小。
  • 我尝试使用 ID 列与日期列直接访问历史记录表的行。这是事情更有趣的地方。我可以在 ~400 毫秒内访问表中较旧的行,而与 AS OF 子子句一样,它需要 ~1200 毫秒。我尝试在日期列上的测试表上进行过滤,并注意到与 ID 列上的过滤相比有类似的减速。这让我相信日期比较是一些放缓的原因。

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

执行计划

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

较旧的行~1200 毫秒执行时间

最近行~350ms 执行时间

表详细信息

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

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

Jos*_*ell 8

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

...看起来您的问题的一部分是您正在读取 5000 万行以便在计划中返回 20K。

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

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

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

当前表上的 NC 索引,以SysEndTime

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

这将允许您通过寻找适当的结束时间来避免读取当前表中的某些行。

历史表上的 CCI

CREATE CLUSTERED COLUMNSTORE INDEX ix_BenefitsHistory
ON dbo.BenefitsHistory
WITH (DROP_EXISTING = ON);
Run Code Online (Sandbox Code Playgroud)

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

当前表上的 NC 索引,以SysStartTime

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

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

创建上面概述的 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 索引,转而使用聚集列存储索引。在我的测试中,性能与上述索引解决方案非常相似。