2010-08-26 53 views
4

我有一個很大的查詢,其中一個簡單的子查詢優化從8分鐘下降到20秒。我不確定我明白爲什麼優化會產生如此激烈的效果。爲什麼這個(不相關的)子查詢導致這樣的問題?

從本質上說,這裏的問題部分:

SELECT (bunch of stuff) 
FROM 
    a LEFT OUTER JOIN b ON a.ID = b.a 
    LEFT OUTER JOIN c ON b.ID = c.b 
    ... 
    ... 
     INNER JOIN veryLargeTable 
     ON a.ID = veryLargeTable.a 
     AND veryLargeTable.PetID = 
      (SELECT id from Pets WHERE Pets.Name = 'Something') /* BAD! */ 
    ... 
    ... 

在所有的,有16個連接的表。如果我更換veryLargeTable的第二謂詞與含有petID(而不是使用子查詢)預填充的變量加入整個查詢加快急劇

AND veryLargeTable.PetID = @petID /* Awesome! */ 


顯然,當正在執行(SELECT id from Pets WHERE Name = 'Something')爲每一行。有兩件事我不完全明白:

  1. 據我所知,這是一個不相關的子查詢。 Pets表根本不是外部查詢的一部分。是不是非相關的子查詢獨立評估(並因此優化)?爲什麼這裏不是這種情況?

  2. 執行計劃顯着不同。在上面的失敗案例中,整個子樹處理估計的950k行。在win情況下(使用變量而不是子查詢),估計的行只有大約125k。這是怎麼回事?爲什麼有更多的行涉及如果該子查詢在那裏? Pets.Name列肯定有唯一的數據(但據我所知,沒有唯一的約束)。

請注意,將謂詞移至WHERE子句不會影響查詢,正如我所期望的那樣,因爲它是INNER JOIN。

深入瞭解!

+0

使用變量可能導致不同的計劃。它通常會導致更糟糕的計劃,因爲變量的值在編譯時並不知道。也許你在這個場合很幸運。也許專注於實際計劃中的估計行數與實際行數,以查看是否有任何可能的統計問題。當您查看緩慢運行的實際執行計劃時,您是否可以看到多次執行的子查詢? – 2010-08-26 17:29:33

+0

@Martin Smith - 我可以看到正在執行的查詢作爲索引查找,並將其作爲其他輸入放入帶有RID查找的嵌套循環中。這是非常低的成本 - 但令人驚訝的是,進一步的一些操作,它將它推到哈希匹配與非常大表中的集羣索引掃描,這是一個巨大的成本。在查詢的好版本中 - 這些操作都不存在。 – womp 2010-08-26 17:52:43

回答

4

據我的經驗,更復雜的查詢中獲取,能力稍遜的SQL優化器是創建麻利計劃。在這裏,你有16個連接,有些或大部分是外連接,你至少有一個子查詢......足夠的索引,基數,視圖,外部應用,誰知道還有甚麼,甚至沒有人,甚至微軟工程師*可以找出能夠統一定期生成最優方案的程序。

你所描述的,我經歷過很多次 - 在一個混亂的查詢中改變一個簡單的事情,並且所有事情都快了一個數量級(或者,牙齒變得更慢)。我沒有辦法確定什麼時候複雜過於複雜,這比什麼都更有感覺。我的一般經驗法則是,如果它看起來太長或太複雜,請簡化你可以在哪裏 - 例如你預先選擇的單一嵌套值,或者突破部分查詢,而不是總是快速運行,結果很小設置並首先運行它並將結果存儲在臨時表中。

(*請注意,這是溫和的sarcsam)

4

作爲替代方案,我想你可以消除子查詢有:

... 
INNER JOIN veryLargeTable vLT 
    ON a.ID = vLT.a 
INNER JOIN Pets p 
    ON vLT.PetID = p.id 
     and p.Name = 'Something' 
... 
0

我個人認爲結果並不奇怪,如果有上Pets.Name沒有索引。如果您在Pets.Name上創建唯一索引,則可能會看到更好的結果。如果沒有從服務器角度的索引,子查詢可能會返回多行或NULL。也許優化者可以做得更好;它經常需要幫助。

+0

這個想法已經超越了我的想法,但查詢是不相關的,所以我一直認爲它會被獨立評估。我會嘗試創建約束,看看會發生什麼。 – womp 2010-08-27 15:58:15

0

原因就像您指出的那樣,並且從我的經驗來看,通常甚至最簡單的不相關子查詢通常都是由SQL Server的查詢優化器重新計算的。

例如,您可以查看以下查詢的執行計劃,並查看非相關子查詢是否已重新計算。

SELECT ID 
FROM #table1 
WHERE ID in (SELECT ID from #table1) 
UNION ALL 
SELECT ID 
FROM #table1 
WHERE ID in (SELECT ID from #table1) 

這是有或沒有聚集索引的屬性,在這種情況下,「ID」。正如有人指出的,你可以重寫這個查詢來使用連接而不是子查詢。然而,在許多情況下可以完成,如果子查詢返回聚合標量,例如

where ID = (select MAX(ID) from #table1) 

然後聯接重寫可能無法如此輕鬆地工作。