如果在WHERE子句中使用函数,则SQL查询时间会从1秒跳到1分钟以上

时间:2010-02-05 09:28:44

标签: sql sql-server-2008

我在MS SQL中有这个查询非常奇怪(至少从我的角度来看)。

我有一个名为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分钟。任何人都可以解释为什么会这样吗?

3 个答案:

答案 0 :(得分:7)

在SQL Server中假定函数不是纯粹的,这意味着查询优化器不会缓存函数的结果并重新使用它;每次引用时都会调用该函数。对于只返回数字的简单函数来说,这是正确的(正如我们在使用函数模拟常量的项目中发现的那样成本......)。

因此,在第一个版本中,当您调用该函数时,该函数将被调用一次,并且结果将手动缓存并在查询中重复使用。但是在第二个版本中,当WHERE子句尝试匹配行时,将为每一行调用该函数。如果你有很多行,那么每行几毫秒开始加起来。

(另请注意,您的查询在语义上是不同的。在第一个查询中,您说的是“事物与我在开始时评估的函数的结果相同”,而在第二个查询中,您说的是“事情与我在这个特定实例中评估的函数的结果相同,因为我考虑了行“。因为你的函数使用SELECT语句,然后 - 取决于事务隔离级别 - 它可以如果基础数据发生变化,可能会为不同的行返回不同的结果。)

答案 1 :(得分:2)

在第二个代码示例中,为结果连接表中的每一行调用该函数。会有很多这样的。

首先,它只被召唤一次。

答案 2 :(得分:0)

数据库服务器显然不够聪明,无法决定它只能评估一次函数,然后将其用作索引中的常量。

这是MS SQL的旧版本吗?

此外,如果MS-SQL有这样的选项,您可能需要以某种方式声明函数是确定性的(为同一输入返回相同的值)。

更新:刚刚看到你的功能“只是在一个表中与其他几个人一起检查TOP 1条目。”这意味着该函数不是确定性的,并且不独立于数据库数据。优化器无法加快速度。