奇怪的SQL Server查询问题

时间:2009-04-22 08:45:14

标签: sql sql-server sql-server-2005 stored-procedures

所以我对SQL Server存储过程有这个奇怪的问题。基本上我有这个漫长而复杂的程序。像这样:

SELECT Table1.col1, Table2.col2, col3
FROM Table1 INNER JOIN Table2
     Table2 INNER JOIN Table3
     -----------------------
     -----------------------
     (Lots more joins)
WHERE Table1.Col1 = dbo.fnGetSomeID() AND (More checks)
     -----------------------
     -----------------------
(6-7 More queries like this with the same check)

问题是检查最后的WHERE子句 Table1.Col1 = dbo.fnGetSomeID()。函数dbo.fnGetSomeID()返回一个简单的整数值 1 。所以当我硬编码值 1 时,函数调用应该是SP只需要大约15秒。但是当我用WHERE子句中的那个函数调用替换它时,大约需要3.5分钟。

所以我这样做:

DECLARE @SomeValue INT
SET @SomeValue = dbo.fnGetSomeID()
--Where clause changed
WHERE Table1.Col1 = @SomeValue

所以现在这个函数只被调用一次。但仍然是3.5分钟。所以我继续这样做:

DECLARE @SomeValue INT
--Removed the function, replaced it with 1
SET @SomeValue = 1
--Where clause changed
WHERE Table1.Col1 = @SomeValue

仍然需要3.5分钟。为什么性能影响?以及如何让它消失?

6 个答案:

答案 0 :(得分:2)

即使@SomeValue设置为1,当你有

WHERE Table1.Col1 = @SomeValue

SQL Server可能仍然将@SomeValue视为变量,而不是硬编码1,这会相应地影响查询计划。并且由于Table1链接到Table2,而Table2链接到Table3等,因此运行查询的时间量被放大了。另一方面,当你有

WHERE Table1.Col1 = 1

查询计划被锁定,Table1.Col1的常量值为1.只是因为我们看到了

WHERE Table1.Col1 = @SomeValue

作为'hardcoding',并不意味着SQL以同样的方式看待它。每个可能的笛卡尔积都是候选产品,需要对每个产品进行@SomeValue评估。 因此,标准建议适用 - 检查执行计划,如果需要,重写查询。

此外,这些连接列是否已编入索引?

答案 1 :(得分:1)

正如其他地方所提到的,执行计划的差异取决于您采取的方法。我会看两个执行计划,看看那里是否有明显的答案。

This question描述了一个类似的问题,在这种情况下答案结果涉及连接设置。

我自己也遇到了几乎 完全 same problem,我在那种情况下发现的是使用更新的结构( SQL 2008中的分析函数显然混淆了优化器。对于您来说情况可能并非如此,因为您正在使用SQL 2005,但根据查询的其余部分,可能会发生类似情况。

另一件需要注意的事情是,您是否对Table1.Col1的值有偏差 - 如果优化器在使用函数或变量而不是常量时使用通用执行计划,则可能导致它选择次优连接比选择次数连接时可以清楚地看到该值是一​​个特定的常数。

如果所有其他方法都失败了,并且此查询不在另一个UDF中,则可以像预测一样预先计算fnGetSomeID()UDF的值,然后将整个查询包装在动态SQL中,在SQL字符串中将值作为常量提供。这应该会给你带来更快的性能,代价是每次重新编译查询(在这种情况下这应该是一个很好的交易)。

答案 2 :(得分:0)

另一件事要尝试。 不是将id加载到变量中,而是将其加载到表中

if object_id('myTable') is not null drop myTable
select dbo.fnGetSomeID() as myID into myTable

然后使用

WHERE Table1.Col1 = (select myID from myTable)
在您的查询中

答案 3 :(得分:0)

您可以尝试使用OPTIMIZE FOR提示强制执行给定常量的计划,但结果可能不一致;在2008年,您可以使用OPTIMIZE FOR UNKNOWN

答案 4 :(得分:0)

我认为既然优化器不知道该函数有多少工作,它会尝试最后评估它们。

我会尝试提前在变量中存储函数的返回值,并在where子句中使用它。

此外,您可能希望尝试使用模式绑定您的函数,因为有时它显然是seriously affects peformance.

您可以将您的函数架构绑定为:

create function fnGetSomeID()
with schema_binding
returns int
... etc.

答案 5 :(得分:0)

 (Lots more joins)
     

WHERE Table1.Col1 = dbo.fnGetSomeID()AND(More checks)

这不是一个很好的问题。最后,值是否由函数或子查询或变量返回或者是常量是无关紧要的。但它确实如此,并且在某种程度上复杂化,很难获得一致的结果。你无法真正调试它,因为你和这里的任何人都不能在查询优化器的黑匣子内对等。你所能做的只是戳它,看看它是如何表现的。

我认为查询优化器的行为不规律,因为查询中有很多表。当你告诉它寻找1时,它会查看索引统计信息并做出不错的选择。当你告诉它什么时,它假定它应该根据它做什么加入 知道,不信任你的函数/变量来返回一个选择性值。为此,Table1.Col1必须具有不均匀的值分布。或者查询优化器不是最优的。

无论哪种方式,估计的查询计划都应该显示出差异。寻找添加(或有时删除)索引的机会。在许多情况下,3.5计划可能是合理的,服务器真正想要的是更好的索引。

除此之外是猜测。有时候,痛心地说,答案在于找到能产生一小行的表的子集,把它们放在一个临时表,并加入该对表的其余部分。 OPTIMIZE FOR提示也可能有用。

但请记住,您提供的任何解决方案都将依赖于脆弱,数据和版本。