2009-04-22 131 views
2

所以我有这个奇怪的问题与SQL Server存储过程。基本上我有这个漫长而复杂的过程。是这样的:奇怪的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) 

的问题是在结束Table1.Col1 = dbo.fnGetSomeID() WHERE子句中该检查。函数dbo.fnGetSomeID()返回一个简单的整数值。所以当我硬编码值为,其中函数调用应该是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分钟。为什么性能会影响?如何让它消失?

+0

需要多长时间自己执行`dbo.fnGetSomeID()` – 2009-04-22 08:53:14

+0

不到一秒钟。 – 2009-04-22 08:57:11

回答

0

另一件尝试。 不必加载标识加入一个变量,将其加载到表

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

,然后在查询中使用

WHERE Table1.Col1 = (select myID from myTable) 

1

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

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

我也碰到差不多确切same problem因为这样我自己,我在这种情况下,发现是使用较新的结构(于2008年SQL分析功能)显然是混淆了优化。这可能不适用于您,因为您使用的是SQL 2005,但根据您的其他查询可能会发生类似的情况。

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

如果一切都失败了,并且这个查询不在另一个UDF内,那么可以像计算一样预先计算fnGetSomeID()UDF的值,然后将整个查询包含在动态SQL中,并将该值作为SQL中的常量提供串。这应该会让你获得更快的性能,但这是以每次重新编译查询为代价的(在这种情况下,这应该是一个很好的交易)。

2

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

WHERE Table1.Col1 = @SomeValue 

SQL服务器可能仍然意见@SomeValue作为一个变量,而不是一个硬编码的1,这将影响相应的查询计划。并且由于Table1链接到Table2,并且Table2链接到Table3等,所以运行查询的时间量被放大。在另一方面,当你有

WHERE Table1.Col1 = 1 

查询计划被在1恒定值,只是因为我们看到

WHERE Table1.Col1 = @SomeValue 

为“硬编码”,没有按”与Table1.Col1锁定这意味着SQL以同样的方式看待它。每个可能的笛卡尔产品都是候选产品,每个产品都需要评估@SomeValue。 因此,标准建议适用 - 检查您的执行计划,如果需要重写查询。

另外,这些连接列索引?

0

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

0

我认为,由于优化器不知道该函数做了多少工作,所以它最后尝试对它们进行评估。

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

此外,你可能想尝试架构绑定你的功能,因为很明显,有时它seriously affects peformance.

你可以让你的函数绑定到架构,如下所示:

create function fnGetSomeID() 
with schema_binding 
returns int 
... etc. 
0
(Lots more joins) 

WHERE表1。 Col1 = dbo.fnGetSomeID()AND(更多检查)

这不是一个好问题。最后,该值是否由函数或子查询或变量返回,或者是常量都无关紧要。但确实如此,并且在某种程度上的复杂性很难得到一致的结果。而且你不能真正调试它,因为你或这里的任何人都不能在作为查询优化器的黑盒子内部进行同步。你所能做的就是捅它,看看它是如何表现的。

我认为查询优化器行为不正常,因为查询中有许多表。当你告诉它寻找1它看起来在索引统计,并作出一个不错的选择。当你告诉它其他任何东西时,它都会假定它应该基于它的连接 知道,不信任你的函数/变量返回一个选择性值。为此,Table1.Col1必须具有不均匀的值分布。或者查询优化器不是,这是最优的。

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

除此之外就是猜测。有时候,可悲的是,答案在于找到产生一小排行的表的子集,将它们放在一个临时表中,并将其加入到其余表中。 OPTIMIZE FOR提示也可能有用。但请记住,任何您使用的解决方案都是脆弱的,依赖于数据和版本。