为什么更快地插入和加入#temp表?

时间:2009-05-28 16:42:24

标签: sql sql-server sql-server-2005 tsql

我的查询类似于

SELECT
 P.Column1,
 P.Column2,
 P.Column3,
 ...
 (
   SELECT
       A.ColumnX,
       A.ColumnY,
       ...
   FROM
      dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A
   WHERE
      A.Key = P.Key
   FOR XML AUTO, TYPE  
 ),
 (
   SELECT
       B.ColumnX,
       B.ColumnY,
       ...
   FROM
      dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B
   WHERE
      B.Key = P.Key
   FOR XML AUTO, TYPE  
 )
FROM
(
   <joined tables here>
) AS P
FOR XML AUTO,ROOT('ROOT') 

P有~5000行 每个A和B~4000行

此查询的运行时性能约为10 +分钟。

然而将其更改为:

SELECT
 P.Column1,
 P.Column2,
 P.Column3,
 ...
INTO #P

SELECT
 A.ColumnX,
 A.ColumnY,
 ...
INTO #A     
FROM
 dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A

SELECT
 B.ColumnX,
 B.ColumnY,
 ...
INTO #B     
FROM
 dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B


SELECT
 P.Column1,
 P.Column2,
 P.Column3,
 ...
 (
   SELECT
       A.ColumnX,
       A.ColumnY,
       ...
   FROM
      #A AS A
   WHERE
      A.Key = P.Key
   FOR XML AUTO, TYPE  
 ),
 (
   SELECT
       B.ColumnX,
       B.ColumnY,
       ...
   FROM
      #B AS B
   WHERE
      B.Key = P.Key
   FOR XML AUTO, TYPE  
 )
FROM #P AS P
FOR XML AUTO,ROOT('ROOT')      

性能约为4秒。

这没有多大意义,因为看起来插入临时表然后默认情况下连接的成本应该更高。我倾向于SQL正在使用子查询执行错误的“连接”类型,但也许我错过了它,没有办法指定连接类型用于相关子查询。

有没有办法在没有通过索引和/或提示使用#temp tables / @ table变量的情况下实现这一目标?

编辑:请注意,dbo.TableReturningFunc1和dbo.TableReturningFunc2是内联TVF,而不是多语句,或者它们是“参数化”视图语句。

8 个答案:

答案 0 :(得分:15)

正在为P中的每一行重新评估您的程序。

对临时表执行的操作实际上是缓存存储过程生成的结果集,因此无需重新评估。

快速插入临时表是因为它不会生成redo / rollback

联接也很快,因为拥有稳定的结果集可以创建一个Eager SpoolWorktable的临时索引

您可以使用CTE重复使用没有临时表的过程,但为了提高效率,SQL Server需要实现CTE的结果。

你可以尝试强制它在子查询中使用ORDER BY来执行此操作:

WITH    f1 AS
        (
        SELECT  TOP 1000000000
                A.ColumnX,
                A.ColumnY
        FROM    dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A
        ORDER BY
                A.key
        ),
        f2 AS
        (
        SELECT  TOP 1000000000
                B.ColumnX,
                B.ColumnY,
        FROM    dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B  
        ORDER BY
                B.Key
        )
SELECT  …

,这可能会导致优化程序生成Eager Spool

然而,这远非得到保证。

保证的方法是在查询中添加OPTION (USE PLAN)并将对应的CTE包装到Spool子句中。

请参阅我的博客中有关如何执行此操作的条目:

这很难维护,因为每次重写查询时都需要重写计划,但这样做效果很好而且非常有效。

但是,使用临时表会更容易。

答案 1 :(得分:4)

这个答案需要与Quassnoi的文章一起阅读 http://explainextended.com/2009/05/28/generating-xml-in-subqueries/

通过明智地应用CROSS APPLY,您可以强制内联TVF的缓存或快捷方式评估。此查询立即返回。

SELECT  *
FROM    (
        SELECT  (
                SELECT  f.num
                FOR XML PATH('fo'), ELEMENTS ABSENT
                ) AS x
        FROM    [20090528_tvf].t_integer i
        cross apply (
            select num
            from [20090528_tvf].fn_num(9990) f
            where f.num = i.num
            ) f
) q
--WHERE   x IS NOT NULL -- covered by using CROSS apply
FOR XML AUTO

你没有提供真正的结构,所以很难构建有意义的东西,但这种技术也应该适用。

如果你将Quassnoi文章中的多语句TVF更改为内嵌TVF,计划变得更快(至少一个数量级),计划神奇地减少到我无法理解的东西(这太简单了!)。 / p>

CREATE FUNCTION [20090528_tvf].fn_num(@maxval INT)  
RETURNS TABLE
AS RETURN 
        SELECT  num + @maxval   num
        FROM    t_integer  

统计

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

(10 row(s) affected)
Table 't_integer'. Scan count 2, logical reads 22, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 2 ms.

答案 2 :(得分:2)

子查询引用外部查询时出现问题,这意味着必须为外部查询中的每一行编译和执行子查询。 您可以使用派生表,而不是使用显式临时表。 为简化您的示例:

SELECT P.Column1,
       (SELECT [your XML transformation etc] FROM A where A.ID = P.ID) AS A

如果P包含10,000条记录,则SELECT A.ColumnX FROM A,其中A.ID = P.ID将执行10,000次。
您可以改为使用派生表:

SELECT P.Column1, A2.Column FROM  
P LEFT JOIN 
 (SELECT A.ID, [your XML transformation etc] FROM A) AS A2 
 ON P.ID = A2.ID

好吧,不是那个说明性的伪代码,但基本思想与临时表相同,只是SQL Server在内存中完成所有操作:它首先选择“A2”中的所有数据并构造临时表在内存中,然后加入它。这样您就不必自己选择TEMP。

只是为了给你一个在另一个背景下的原则的例子,它可能会更直接的意义。考虑员工和缺勤信息,您希望显示每位员工记录的缺勤天数。

错误:(运行与数据库中的员工一样多的查询)

SELECT EmpName, 
 (SELECT SUM(absdays) FROM Absence where Absence.PerID = Employee.PerID) AS Abstotal        
FROM Employee

好:(仅运行两次查询)

SELECT EmpName, AbsSummary.Abstotal
FROM Employee LEFT JOIN
      (SELECT PerID, SUM(absdays) As Abstotal 
       FROM Absence GROUP BY PerID) AS AbsSummary
ON AbsSummary.PerID = Employee.PerID

答案 3 :(得分:1)

使用中间临时表可能会加速查询有几个可能的原因,但在您的情况下,最有可能的是被调用(但未列出)的函数可能是多语句TVF,而不是-line TVF。多语句TVF对其调用查询的优化是不透明的,因此优化器无法判断是否存在重用数据或其他逻辑/物理运算符重新排序优化的机会。因此,每次包含的查询应该生成带有XML列的另一行时,它所能做的就是重新执行TVF。

简而言之,多语句TVF挫败了优化者。

按(典型)偏好顺序的常用解决方案是:

  1. 将有问题的多语句TVF重写为内联TVF
  2. 将函数代码嵌入到调用查询中,或
  3. 将有问题的TVF数据转储到临时表中。这就是你所做的......

答案 4 :(得分:0)

考虑将WITH common_table_expression构造用作现在的子选择或临时表,请参阅http://msdn.microsoft.com/en-us/library/ms175972(SQL.90).aspx

答案 5 :(得分:0)

  

这并不是很有意义   似乎是插入一个的成本   临时表,然后做连接应该   de>更高这没有多大意义   似乎是插入一个的成本   临时表,然后做连接应该   默认情况下会更高。

使用临时表,您可以指示Sql Server使用哪个中间存储。但是如果你把所有东西存放在一个大查询中,Sql Server将自行决定。差别并不是那么大;在一天结束时,使用临时存储,无论您是否将其指定为临时表。

在您的情况下,临时表的工作速度更快,为什么不坚持使用呢?

答案 6 :(得分:0)

我同意,Temp表是一个很好的概念。当行计数在一个表中增加一个4000万行的例子并且我想通过在其他表中应用连接来更新表上的多个列时,我总是更喜欢使用公用表表达式来更新select语句中的列case语句,现在我的select语句结果集包含更新的行。使用case语句将4千万条记录插入临时表,使用case语句需要21分钟,然后创建索引需要10分钟,所以我的插入和索引创建时间需要30分钟。然后我将通过将临时表更新结果集与主表结合来应用更新。从4000万条记录中更新1000万条记录需要5分钟,因此我的1000万条记录的总体更新时间大约需要35分钟,而公共表格表达方式需要5分钟。在这种情况下我的选择是公用表表达式。

答案 7 :(得分:-2)

如果临时表在特定实例中变得更快,则应改为使用表变量。

这里有一篇关于差异和绩效影响的好文章:

http://www.codeproject.com/KB/database/SQP_performance.aspx