2010-02-05 67 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

相关问题