【问题标题】:Awkward JOIN causes poor performance尴尬的 JOIN 导致性能不佳
【发布时间】:2014-02-18 18:46:40
【问题描述】:

我有一个存储过程,它通过UNION ALL 组合来自多个表的数据。如果传递给存储过程的参数不适用于特定表,我会尝试使用“帮助位”来“短路”该表,例如@DataSomeTableExists 并在 WHERE 子句中添加相应的条件,例如WHERE @DataSomeTableExists = 1

存储过程中的一个(伪)表有点尴尬,让我有些悲伤。

DECLARE @DataSomeTableExists BIT = (SELECT CASE WHEN EXISTS(SELECT * FROM #T WHERE StorageTable = 'DATA_SomeTable') THEN 1 ELSE 0 END);
...  

UNION ALL

SELECT *
FROM REF_MinuteDimension AS dim WITH (NOLOCK)  
CROSS JOIN  (SELECT * FROM #T WHERE StorageTable = 'DATA_SomeTable') AS T       
CROSS APPLY dbo.fGetLastValueFromSomeTable(T.ParentId, dim.TimeStamp) dpp
WHERE @DataSomeTableExists = 1 AND dim.TimeStamp >= @StartDateTime AND dim.TimeStamp <= @EndDateTime

UNION ALL

...

注意:REF_MinuteDimension 只不过是带有分钟增量的 smalldatetimes。

(1) 执行计划(如下)指示嵌套循环运算符上的警告,指出没有连接谓词。这可能不好,但表之间确实没有自然连接。有没有更好的方法来编写这样的查询?对于 T 中的每个 ParentId,我想要 @StartDateTime 和 @EndDateTime 之间每一分钟的 UDF 值。

(2) 即使在@DataSomeTableExists = 0 时,此查询中的表上仍有 I/O 活动,正如 SET STATISTICS IO ON 和实际执行计划所报告的那样。执行计划报告了 14.2 % 的成本,考虑到这些表格甚至不适用于这种情况,这太高了。

SELECT * FROM #T WHERE StorageTable = 'DATA_SomeTable' 空空返回。

我的查询是这样写的吗?为什么辅助位或空的 T 不会短路此查询?

【问题讨论】:

  • 您应该将 DECLARE 更改为:declare @DataSomeTableExists bit = 0 if exists(SELECT * FROM #T WHERE StorageTable = 'DATA_SomeTable') begin set @DataSomeTableExists = 1 end。这很难阅读,所以这里是pastebin。为什么在CROSS JOIN 查询中也没有“短路”WHERE 参数?我应该提一下,我不知道这是否会起作用,因为这是编写存储过程的一种奇怪的方式。

标签: sql-server performance tsql sql-server-2012 sql-execution-plan


【解决方案1】:

由于您的查询依赖于运行时变量,请考虑使用动态 SQL 动态创建查询。通过这种方式,您可以包含您想要的表格并排除您不想要的表格。

动态SQL有缺点,所以read up

【讨论】:

  • 正如@jean 所建议的,一个更好的选择是创建一个存储过程,分别用于何时需要提取数据以及何时不需要提取数据。然后,从主存储过程中,有条件地触发一个存储过程或另一个。然后,您使用的是 动态逻辑(并保留查询计划),而不是 动态 SQL(这几乎从未真正需要,因此应避免使用)。
【解决方案2】:

我应该提一下,我不知道这是否可行,因为它是一种奇怪的编写存储过程的方式,并且查询优化器不能很好地理解表值 UDF。您可能必须根据IF 语句有条件地将结果集构建到表变量或临时表中,然后返回该数据。但我会先试试这个:

--helper bit declared
declare @DataSomeTableExists BIT = 0x0
if exists (select 1 from #T where StorageTable = 'DATA_SomeTable')
begin
    set @DataSomeTableExists = 0x1
end

...  

UNION ALL

SELECT *
FROM REF_MinuteDimension AS dim WITH (NOLOCK)  
CROSS JOIN  (SELECT * FROM #T WHERE StorageTable = 'DATA_SomeTable' and @DataSomeTableExists = 0x1) AS T
CROSS APPLY dbo.fGetLastValueFromSomeTable(T.ParentId, dim.TimeStamp) dpp
WHERE @DataSomeTableExists = 0x1 AND dim.TimeStamp >= @StartDateTime AND dim.TimeStamp <= @EndDateTime

UNION ALL

...

如果您还不知道,UDF 可能会在执行计划中为您提供奇怪的读数。我知道的不够多,无法为您提供准确的数据,但您应该四处搜索以了解这些限制。

【讨论】:

  • 感谢您的回复。这似乎可以解决问题。好奇为什么您更改了辅助位声明?这只是为了可读性吗?
  • 是的。您正在声明一个变量,将其设置为默认值,同时还使用 EXISTS 和子查询 SELECT 进行查询。当有其他方法可以轻松编写它时,下一个开发人员可以坐下来阅读。
【解决方案3】:

对于 2) 我可以肯定地说那句话

CROSS JOIN  (SELECT * FROM #T WHERE StorageTable = 'DATA_SomeTable') AS T 

我会强制 #T 被分析并进入一个连接。您可以创建带有和不带有该连接的 SP 版本,并使用该标志来执行一个或另一个,但我不能说这会节省任何响应时间||cpu 时钟||I/O 带宽||内存。

对于 1) 如果您使用的是 SQL Server 2005 或更高版本,我建议删除 (nolock) 并密切关注该 UDF。没有一个好的 SQL 小提琴就不能说更多了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-26
    • 2012-09-05
    相关资源
    最近更新 更多