Dav*_*der 5 performance sql-server sql-server-2012 greatest-n-per-group query-performance
我有一个非常复杂的数据模型。如果不解释正在建模的内容,我将无法理解大多数 SQL 示例,因此我将尝试进行解释。
Mainlines -> Releases -> Overlays -> Calibrations <- Parameters
Calibrations 是 Mainline-Release-Overlay 链和 Parameters 的子节点。这是我想退货的套装。
现在,复杂性在于——为了节省空间——我们存储基本 Mainline 的校准,然后只存储在发布和覆盖级别发生更改时的差异。这会产生大约 16K 行的“基本”校准集,然后每个版本有几百个更改,每个覆盖可能只有几个更改。发生参数删除。为了跟踪这一点,校准有一个状态字段 (tinyint),它被设置为 1 以进行删除。
叠加层依次进行版本控制。因此,要获得完整的“校准”,我们需要查询校准表以获取最新版本的参数数据,直至特定的叠加版本号。参数元数据可能会改变,但名称保持不变,因此这些校准可能指的是不同版本的参数,尽管名称相同。
到目前为止,以下内容已经完美且即时地运行(有以下注意事项):
SELECT c.Parameter_ParameterID, p.Designation AS Name, c.Data, o.Version
FROM Calibrations c, Parameters p, Overlays o, Releases r
WHERE r.Mainline_MainlineID = 9
AND o.Release_ReleaseID = r.ReleaseID
AND c.Overlay_OverlayID = o.OverlayID
AND c.Parameter_ParameterID = p.ParameterID
AND o.Version =
(SELECT MAX(o1.Version)
FROM Parameters p1, Calibrations c1, Overlays o1, Releases r1
WHERE r1.Mainline_MainlineID = 9
AND o1.Release_ReleaseID = r1.ReleaseID
AND c1.Overlay_OverlayID = o1.OverlayID
AND c1.Parameter_ParameterID = p1.ParameterID
AND p1.Designation = p.Designation -- New condition
AND o1.Version <= 68)
AND c.Status != 1 -- Changed from boolean to tinyint
ORDER BY Version DESC, Name
Run Code Online (Sandbox Code Playgroud)
自从我们开始(正确)跟踪参数版本以来,问题就出现了,因此参数的内部匹配不能再基于 ParameterID,而是基于名称(即名称)。起初这不是问题,但现在校准状态不再是用于删除的布尔值。现在它也可以设置为 2 为新引入,3 为重新插入。
添加这两个更改导致此查询需要 10-15 分钟才能运行!这怎么可能呢?即使我扭曲查询以将每一行连接到其他每一行,也不应该花这么长时间!
这里有两件令人抓狂的事情。一个是,当这最初成为一个问题时,我在参数指定字段上放置了一个索引,它使它再次起作用。现在,删除和重新创建它没有区别。此外,还有一个特殊情况,即如果我在数据库中正好有两条主线,它仍然会立即运行。如果我只有一个或两个以上,则需要永远。
我尝试了各种注释部分,例如o1.Version <=条件和c.Status !=条件,有时,它会再次起作用。我无法处理各种情况下的差异,而且我已经束手无策了。我知道我离让它再次快速运行还有一点小技巧,但这显然超出了我的能力,这是我下个月测试计划的一个障碍。
我已经编程 35 年了,但我以前从未像这样处理过 SQL,而且 SSMS 用于显示解释计划的工具对我来说毫无意义。我想了解基本问题并得到解决。
Pau*_*ite 18
完整的分析需要访问执行计划、表和索引定义以及数据库统计信息(或数据库本身的副本)。这可能是不切实际的,所以这里有一些一般性观察,以及您可以尝试的可能解决方案。(严格来说,这个问题可能超出了本网站的范围。)
一般背景
SQL Server 查询优化器是负责为查询表示的逻辑结果规范选择执行计划的组件。即使是适度复杂的查询,也有大量可能的物理计划。优化器使用启发式和成本估算从它探索的有限计划空间中进行选择。
提供给优化器的信息质量(包括数据库设计、索引和当前统计数据的准确性)都对优化器选择的计划在实践中表现良好的可能性有很大影响。如果设计是相关的、索引良好的并且具有代表性的统计数据,那么好的计划选择将是常态。否则,所有赌注都将取消。
有问题的查询相当复杂,虽然它的意图对大多数人来说相当清楚,但优化器将其视为与任何其他查询一样的 SQL 查询。具有多个连接和MAX聚合的相关子查询意味着潜在的计划搜索空间将很大,并且估计可能不准确(由于累积错误,如果没有其他原因)。优化器很可能最终会选择一个计划,如果它的假设得到证实,它会运行良好,但在现实中可能会表现得很糟糕。
查询和潜在的重写
从广义上讲,您似乎在向 SQL Server 要求每个指定的最高版本,但对版本号有限制。表达此查询的另一种方法(可能具有更可预测的性能)是对行进行编号(适当分区和排序),然后返回每组排名第一的单行:
WITH MaxVersionPerDesignation AS
(
-- Do the joins and number the rows
-- per designation, in descending
-- Version order
SELECT
c.Parameter_ParameterID,
p.Designation AS Name,
c.Data,
o.[Version],
rn = ROW_NUMBER() OVER (
PARTITION BY p.Designation
ORDER BY o.[Version] DESC)
FROM dbo.Releases AS r
JOIN dbo.Overlays AS o
ON o.Release_ReleaseID = r.ReleaseID
JOIN dbo.Calibrations AS c
ON c.Overlay_OverlayID = o.OverlayID
JOIN dbo.[Parameters] AS p
ON p.ParameterID = c.Parameter_ParameterID
WHERE
r.Mainline_MainlineID = 9
AND c.[Status] <> 1
AND o.[Version] <= 68
)
-- Return the row with the highest Version per designation
SELECT
MVPD.Parameter_ParameterID,
MVPD.Name,
MVPD.Data,
MVPD.[Version]
FROM MaxVersionPerDesignation AS MVPD
WHERE
-- Row #1 per Designation
MVPD.rn = 1
ORDER BY
MVPD.[Version] DESC,
MVPD.Designation;
Run Code Online (Sandbox Code Playgroud)
希望上述 SQL 中的逻辑相当容易理解。
上传计划分析
查询优化器生成一个执行计划,该计划尝试对每个外部行执行一次子查询。如果外部查询只生成一行,这将是一个好主意,但遗憾的是它在运行时限定了 16,794 行(使用SQL Sentry Plan Explorer捕获的执行计划):
这种错误估计是您问题的根本原因。最有可能的是,基表上的统计数据并不代表当前数据。您应该刷新这些统计数据并制定一个持续的计划以确保统计数据保持合理的最新状态。错误的 1-row 估计的另一个副作用是优化器认为整个查询的成本非常低,几乎是微不足道的,因此不会花很长时间检查替代方案。
无论如何,子查询(非常类似于从相关一边外部查询和一个额外的谓词)被完全执行16794倍(与不同的值Designation和Version各自的时间)。这当然是一个糟糕的策略(但对于 1 个外排就可以了)。

累积效果是生成超过 2.82 亿行(在所有 16k 次迭代中)。这导致上面显示的计划片段中执行了 2.82 亿次 Key Lookup。后一个事实可能是性能不佳的主要原因,尽管执行 16,794 次相关子查询在任何情况下都不会很快(如果优化器知道 16,794 行将驱动它,则不会选择此策略)。
使用最佳索引(遵循比我现在可以做的更详细的分析),可以使用CROSS APPLYandTOP 1而不是MAX(请参阅此相关问题)重写查询以获得可能的最佳性能,但您可能会找到ROW_NUMBER替代方案(再次,请参阅相关问题)表现足够好,具有良好的计划选择稳定性。
关键查找:
您可以通过包括消除键查找(在长期计划)Parameter_ParameterID,Data以及Status在列IX_Overlay_OverlayID的索引。例如:
CREATE INDEX [IX_Overlay_OverlayID]
ON dbo.Callibrations (Overlay_OverlayID)
INCLUDE (Parameter_ParameterID, Data, Status)
WITH (DROP_EXISTING = ON);
Run Code Online (Sandbox Code Playgroud)
这可能不会导致优化器在不更新统计信息的情况下选择更好或更稳定的计划,但我更不用说它了。
| 归档时间: |
|
| 查看次数: |
882 次 |
| 最近记录: |