2010-02-05 63 views
2

我在MS SQL中有這個查詢,這很奇怪(至少從我的角度來看)。如果在WHERE子句中使用函數,SQL查詢時間從1秒跳到1分鐘以上

我有一個用戶定義的函數調用:dbo.NajblizszaDataWyceny(3,'2010-02-05')這是簡單的檢查TOP 1條目在一個表中加入了其他人。查詢本身需要幾毫秒,所以它不是一個大問題,但我仍然顯示該函數。

CREATE FUNCTION [dbo].[NajblizszaDataWyceny] (@idPortfela INT, @dataWaluty DATETIME) 
RETURNS DATETIME 
AS BEGIN 
RETURN (

SELECT TOP 1   [WycenaData] 
FROM [BazaZarzadzanie].[dbo].[Wycena] t1 
    LEFT JOIN [BazaZarzadzanie].[dbo].[KlienciPortfeleKonta] t3 
    ON t1.[KlienciPortfeleKontaID] = t3.[KlienciPortfeleKontaID] 
    LEFT JOIN [BazaZarzadzanie].[dbo].[KlienciPortfele] t4 
    ON t3.[PortfelID] = t4.[PortfelID] 
WHERE [WycenaData] <= @dataWaluty AND [t3].[PortfelID] = @idPortfela 
ORDER BY [WycenaData] DESC) 
END 

當我使用以下方式這樣的功能:

DECLARE @dataWyceny DATETIME 
SET @dataWyceny = dbo.NajblizszaDataWyceny(3, '2010-02-05') 

SELECT t1.[KlienciPortfeleKontaID], 
    t4.[PortfelIdentyfikator] AS 'UmowaNr', 
    t5.[KlienciRachunkiNumer], 
    [WycenaData], 
    t2.[InISIN] AS 'InstrumentISIN', 
    t2.[InNazwa] AS 'InstrumentNazwa', 
    [WycenaWartosc] 
FROM [BazaZarzadzanie].[dbo].[Wycena] t1 
    LEFT JOIN [BazaZarzadzanie].[dbo].[Instrumenty] t2 
    ON t1.[InID] = t2.[InID] 
    LEFT JOIN [BazaZarzadzanie].[dbo].[KlienciPortfeleKonta] t3 
    ON t1.[KlienciPortfeleKontaID] = t3.[KlienciPortfeleKontaID] 
    LEFT JOIN [BazaZarzadzanie].[dbo].[KlienciPortfele] t4 
    ON t3.[PortfelID] = t4.[PortfelID] 
    LEFT JOIN [BazaZarzadzanie].[dbo].[KlienciRachunki] t5 
    ON t3.[KlienciRachunkiID] = t5.[KlienciRachunkiID] 
    LEFT JOIN [BazaZarzadzanie].[dbo].[WycenaTyp] t6 
    ON t1.[WycenaTyp] = t6.[WycenaTyp] 
WHERE WycenaData = @dataWyceny  AND t3.[PortfelID] = 3 
ORDER BY t5.[KlienciRachunkiNumer], 
    WycenaData 

它需要1秒來運行。但是,當我在WHERE直接把用戶的功能,所以它看起來像:

SELECT t1.[KlienciPortfeleKontaID], 
    t4.[PortfelIdentyfikator] AS 'UmowaNr', 
    t5.[KlienciRachunkiNumer], 
    [WycenaData], 
    t2.[InISIN] AS 'InstrumentISIN', 
    t2.[InNazwa] AS 'InstrumentNazwa', 
    [WycenaWartosc] 
FROM [BazaZarzadzanie].[dbo].[Wycena] t1 
    LEFT JOIN [BazaZarzadzanie].[dbo].[Instrumenty] t2 
    ON t1.[InID] = t2.[InID] 
    LEFT JOIN [BazaZarzadzanie].[dbo].[KlienciPortfeleKonta] t3 
    ON t1.[KlienciPortfeleKontaID] = t3.[KlienciPortfeleKontaID] 
    LEFT JOIN [BazaZarzadzanie].[dbo].[KlienciPortfele] t4 
    ON t3.[PortfelID] = t4.[PortfelID] 
    LEFT JOIN [BazaZarzadzanie].[dbo].[KlienciRachunki] t5 
    ON t3.[KlienciRachunkiID] = t5.[KlienciRachunkiID] 
    LEFT JOIN [BazaZarzadzanie].[dbo].[WycenaTyp] t6 
    ON t1.[WycenaTyp] = t6.[WycenaTyp] 
WHERE WycenaData = dbo.NajblizszaDataWyceny(3, '2010-02-05')  AND t3.[PortfelID] = 3 
ORDER BY t5.[KlienciRachunkiNumer], 
    WycenaData 

它需要1.5分鐘完成。任何人都可以解釋爲什麼會發生?

回答

7

函數在SQL Server中並不被假定爲純粹的,這意味着查詢優化器不會緩存函數的結果並重新使用它;每次引用時都會調用該函數。對於只返回數字的簡單函數(就像我們在使用函數模擬常量的項目中發現的成本)一樣,這也是如此。

因此,在第一個版本中,函數在您調用時調用一次,並且結果被手動緩存並在查詢中重新使用。但是,在第二個版本中,當WHERE子句嘗試匹配行時,將針對每行調用該函數。如果你有很多行,那麼每行幾毫秒開始累加。 (請注意,你的查詢在語義上是不同的,在第一個查詢中你說的「事情和我在開始時評估的函數的結果是一樣的」,而在第二個查詢中你說「事情和函數的結果相同,我在這個特定的實例中按照我認爲該行的時間進行評估」。由於函數使用了SELECT語句,因此 - 根據事務隔離級別 - 它可能會返回不同的值如果底層數據發生變化,結果將顯示在不同的行上)。

+0

除了重新調用函數之外,索引也不能再使用了。這可能會產生更大的影響。 – Thilo 2010-02-05 09:37:21

+0

感謝這真的解釋了很多。 – MadBoy 2010-02-05 09:44:53

2

在第二個代碼示例中,將爲結果連接表中的每一行調用該函數。會有很多這樣的。

在第一個,它只被稱爲一次。

0

數據庫服務器顯然不夠聰明,無法決定它只能評估一次函數,然後將其用作索引中的常量。

這是MS SQL的舊版本嗎?

另外,如果MS-SQL有這樣的選項,您可能需要以某種方式聲明該函數是確定性的(對同一輸入返回相同的值)。

更新:剛纔看到你的功能「很簡單,檢查一個表中的TOP 1項目與其他人一起加入。」這意味着該函數不是確定性的,並且不獨立於數據庫數據。優化器無法加速實現。

+0

這是MS SQL 2008,我也將在2005年運行它。 – MadBoy 2010-02-05 09:38:58

相關問題