我正在尝试提高以下查询的性能:
UPDATE [#TempTable]
SET Received = r.Number
FROM [#TempTable]
INNER JOIN (SELECT AgentID,
RuleID,
COUNT(DISTINCT (GroupId)) Number
FROM [#TempTable]
WHERE Passed = 1
GROUP BY AgentID,
RuleID
) r ON r.RuleID = [#TempTable].RuleID AND
r.AgentID = [#TempTable].AgentID
目前使用我的测试数据大约需要一分钟。我对该查询所在的所有存储过程的更改输入有限,但我可能可以让他们修改这个查询。或者添加索引。我尝试添加以下索引:
CREATE CLUSTERED INDEX ix_test ON #TempTable(AgentID, RuleId, GroupId, Passed)
它实际上使查询花费的时间增加了一倍。我使用非聚集索引获得了相同的效果。
我尝试如下重写它,但没有任何效果。
WITH r AS (SELECT AgentID,
RuleID,
COUNT(DISTINCT (GroupId)) Number
FROM [#TempTable]
WHERE Passed = 1
GROUP BY AgentID,
RuleID
)
UPDATE [#TempTable]
SET Received = r.Number
FROM [#TempTable]
INNER JOIN r
ON r.RuleID = [#TempTable].RuleID AND
r.AgentID = [#TempTable].AgentID
接下来我尝试使用这样的窗口函数。
UPDATE [#TempTable]
SET Received = COUNT(DISTINCT (CASE WHEN Passed=1 THEN GroupId ELSE NULL END))
OVER (PARTITION BY AgentId, RuleId)
FROM [#TempTable]
此时我开始收到错误
Msg 102, Level 15, State 1, Line 2
Incorrect syntax near 'distinct'.
所以我有两个问题。首先你不能用 OVER 子句做一个 COUNT DISTINCT 还是我写错了?其次,任何人都可以提出我尚未尝试过的改进建议吗?仅供参考,这是一个 SQL Server 2008 R2 Enterprise 实例。
编辑:这是原始执行计划的链接。我还应该注意,我的大问题是这个查询运行了 30-50 次。
https://onedrive.live.com/redir?resid=4C359AF42063BD98%21772
EDIT2:这是评论中要求的语句所在的完整循环。我正在与定期与此一起工作的人核实循环的目的。
DECLARE @Counting INT
SELECT @Counting = 1
-- BEGIN: Cascading Rule check --
WHILE @Counting <= 30
BEGIN
UPDATE w1
SET Passed = 1
FROM [#TempTable] w1,
[#TempTable] w3
WHERE w3.AgentID = w1.AgentID AND
w3.RuleID = w1.CascadeRuleID AND
w3.RulePassed = 1 AND
w1.Passed = 0 AND
w1.NotFlag = 0
UPDATE w1
SET Passed = 1
FROM [#TempTable] w1,
[#TempTable] w3
WHERE w3.AgentID = w1.AgentID AND
w3.RuleID = w1.CascadeRuleID AND
w3.RulePassed = 0 AND
w1.Passed = 0 AND
w1.NotFlag = 1
UPDATE [#TempTable]
SET Received = r.Number
FROM [#TempTable]
INNER JOIN (SELECT AgentID,
RuleID,
COUNT(DISTINCT (GroupID)) Number
FROM [#TempTable]
WHERE Passed = 1
GROUP BY AgentID,
RuleID
) r ON r.RuleID = [#TempTable].RuleID AND
r.AgentID = [#TempTable].AgentID
UPDATE [#TempTable]
SET RulePassed = 1
WHERE TotalNeeded = Received
SELECT @Counting = @Counting + 1
END
SQL Server 目前不支持此构造。它可以(并且在我看来应该)在未来的版本中实现。
应用报告此缺陷的反馈项中列出的解决方法之一,您的查询可以重写为:
生成的执行计划是:
这具有避免万圣节保护的急切表假脱机(由于自连接)的优点,但它引入了排序(用于窗口)和通常低效的惰性表假脱机构造来计算并将
SUM OVER (PARTITION BY)
结果应用于所有行在窗口中。它在实践中的表现是一个只有你才能做的练习。整体方法很难使其表现良好。以递归方式将更新(尤其是基于自连接的更新)应用于大型结构可能有利于调试,但它会导致性能下降。重复的大扫描、内存溢出和万圣节问题只是其中的一些问题。索引和(更多)临时表可以提供帮助,但需要非常仔细的分析,特别是如果索引被进程中的其他语句更新(维护索引会影响查询计划选择并添加 I/O)。
最终,解决潜在问题会带来有趣的咨询工作,但对于这个网站来说太多了。我希望这个答案可以解决表面问题。
原始查询的替代解释(导致更新更多行):
注意:消除排序(例如通过提供索引)可能会重新引入对 Eager Spool 或其他东西的需求,以提供必要的万圣节保护。Sort 是一个阻塞算子,所以它提供了全相分离。
死灵术:
使用 DENSE_RANK 模拟分区上不同的计数相对简单:
编辑:
警告:如果有空值,显然您需要添加 WHERE ADR IS NOT NULL。