2009-04-22 83 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提示也可能有用。但請記住,任何您使用的解決方案都是脆弱的,依賴於數據和版本。