Sql server 为什么内部连接(a)2个函数或(b)2个具有相同函数结果的临时表会有如此大的性能差异?

Sql server 为什么内部连接(a)2个函数或(b)2个具有相同函数结果的临时表会有如此大的性能差异?,sql-server,tsql,sql-server-2008-r2,sql-server-2012,Sql Server,Tsql,Sql Server 2008 R2,Sql Server 2012,所以我经常在SQLServer2012中看到这一点,也看到了SQLServer2008R2。假设我有一个查询: Select * From function1(@param1, @param2) f1 INNER JOIN function2(@param1, @param2) f2 ON f1.key = f2.key 这将需要大约5分钟的时间来运行,因为我认为(也许我完全错了,但看起来是这样的)当它从function1获得一个新行时,它会再次计算f

所以我经常在SQLServer2012中看到这一点,也看到了SQLServer2008R2。假设我有一个查询:

  Select *
  From function1(@param1, @param2) f1
       INNER JOIN function2(@param1, @param2) f2 ON
          f1.key = f2.key
这将需要大约5分钟的时间来运行,因为我认为(也许我完全错了,但看起来是这样的)当它从function1获得一个新行时,它会再次计算function2。现在,如果我将其改写为:

 Select *
 Into #f1
 From function1(@param1, @param2)

 Select *
 Into #f2
 From function2(@param1, @param2)

 Select *
 From #f1 f1
     INNER JOIN #f2 f2 on
       f1.key = f2.key

这将需要3秒钟才能运行。我不明白为什么优化器决定以不同的方式评估这些场景。是否有一个提示我可以使用,这样我就不必做这个变通?为什么会发生这种情况?

您无法准确地将临时表的使用与函数调用进行比较。当您创建临时表并运行查询时,编译器知道表的一些重要信息——它们有多大。该信息随后用于执行

我认为函数不会被多次调用,即使在第一种情况下也是如此

因此,我怀疑问题在于表的大小以及随后使用的连接算法。在SQL Server 2014中,您可以尝试使用内存优化表

您也可以尝试CTE,尽管我认为这不会有帮助(因为CTE是在编译后评估的):


另一个选项是使用编译器选项来使用散列或合并联接。

这里发生了一些事情

首先,如果您谈论的是标量UDF,或者使用
交叉应用
/
外部应用
,那么是的,它将为每一行运行这些

然而,在加入两个TVFS的情况下,您需要考虑以下内容:

  • 内联TVF实际上只是可以接受参数的视图。正因为如此,它们的定义,就像视图一样,被插入到使用TVF的查询中。这使得最终结果查询可以优化,这就是为什么它们的性能比多语句TVF好得多的原因。它们可能以您使用它们的方式加入ok

  • 多语句TVFs:

    • 无法进行优化。在查询优化器看来,它们总是返回一定数量的行(我相信是1000行)。如果设置的行数比实际返回的行数高得多或低得多,那么设置的行数实际上会偏离执行计划
    • 没有办法维护其领域的统计数据。另一方面,当您连接临时表的列时,SQLServer将为这些列生成统计信息,并使用这些信息来制定更准确的执行计划
你能做什么?好吧,根据5分钟与3秒的差异,我怀疑你的TVF是多语句而不是内联的。如果可能的话,将它们转换成内联TVF(你会很高兴你这样做了)


除此之外,如果您有一个解决方案可以在3秒钟内工作,而另一个解决方案可以在5分钟内工作,那么真的有问题吗?您还可以通过
create TABLE
而不是
SELECT INTO
来创建这两个临时表,这可能会有一点帮助。

在SQL Server中,CTE没有具体化(例如,与Postgres相反)。它们的行为类似于视图,即它们是内联的。您使用CTE的查询将产生与问题中没有CTE的原始查询完全相同的执行计划。@VladimirBaranov。对的这正是为什么我认为它对查询没有帮助(尽管可能有什么东西会触发优化器以某种方式做出不同的行为)。您可以在这里发布它(如果XML太大,请使用pastebin.com),我们可以确认您的第一个变体是否确实为每一行调用了该函数。(我不认为是这样)我是否可以给优化器一个提示,让它保持内联(而不是使用我的变通方法将其转换为多语句TVF)?@Denis,我不知道。你从来没有提到过把这个代码放到TVF里。如果是这种情况,那么就不能使用临时表,而必须使用表变量。在这种情况下,您将再次遇到性能问题,因为表变量也不会在其列上保留统计信息。它们似乎只有一行,但可以通过使用
选项(重新编译)
来克服这一点,虽然这本身就是一个性能打击,但总比不使用要好,但不确定是否可以在TVF中做到这一点。
with f1 as (
      Select *
      From function1(@param1, @param2)
     ),
     f2 as (
      Select *
      From function2(@param1, @param2)
     )
Select *
From f1 INNER JOIN
     f2
     on f1.key = f2.key;