为什么这个CTE比使用临时表慢得多?

时间:2014-06-10 14:17:24

标签: sql-server sql-server-2008 common-table-expression temp-tables query-performance

自从最近对我们的数据库进行更新以来,我们遇到了一个问题(我做了这个更新,我在这里感到内疚),其中一个使用的查询从那时起就慢得多。我试图修改查询以获得更快的结果,并设法实现我的目标与临时表,这不错,但我不明白为什么这个解决方案比基于CTE的表现更好一个,它执行相同的查询。也许有必要这样做一些表在不同的数据库中?

这是执行不当的查询(在我们的硬件上22分钟):

WITH CTE_Patterns AS (
SELECT 
    PEL.iId_purchased_email_list,
    PELE.sEmail
FROM OtherDb.dbo.Purchased_Email_List PEL WITH(NOLOCK)
    INNER JOIN OtherDb.dbo.Purchased_Email_List_Email AS PELE WITH(NOLOCK) ON PELE.iId_purchased_email_list = PEL.iId_purchased_email_list
WHERE PEL.bPattern = 1
),
CTE_Emails AS (
    SELECT 
        ILE.iId_newsletterservice_import_list, 
        ILE.iId_newsletterservice_import_list_email, 
        ILED.sEmail
    FROM dbo.NewsletterService_import_list_email AS ILE WITH(NOLOCK)
        INNER JOIN dbo.NewsletterService_import_list_email_distinct AS ILED WITH(NOLOCK) ON ILED.iId_newsletterservice_import_list_email_distinct = ILE.iId_newsletterservice_import_list_email_distinct
    WHERE ILE.iId_newsletterservice_import_list = 1000
)
SELECT I.iId_newsletterservice_import_list, 
        I.iId_newsletterservice_import_list_email, 
        BL.iId_purchased_email_list
FROM CTE_Patterns AS BL WITH(NOLOCK)
    INNER JOIN CTE_Emails AS I WITH(NOLOCK) ON I.sEmail LIKE BL.sEmail

当单独运行两个CTE查询时,它超级快(SSMS为0秒,返回122行和13k行),运行完整查询时,sEmail上的INNER JOIN,超级慢(22分钟)

这是一个表现良好的查询,临时表(我们的硬件上为0秒)和eaxct相同的东西,返回相同的结果:

SELECT
    PEL.iId_purchased_email_list,
    PELE.sEmail
INTO #tb1
FROM OtherDb.dbo.Purchased_Email_List PEL WITH(NOLOCK)
    INNER JOIN OtherDb.dbo.Purchased_Email_List_Email PELE ON PELE.iId_purchased_email_list = PEL.iId_purchased_email_list
WHERE PEL.bPattern = 1

SELECT 
    ILE.iId_newsletterservice_import_list, 
    ILE.iId_newsletterservice_import_list_email, 
    ILED.sEmail
INTO #tb2
FROM dbo.NewsletterService_import_list_email AS ILE WITH(NOLOCK)
    INNER JOIN dbo.NewsletterService_import_list_email_distinct AS ILED ON ILED.iId_newsletterservice_import_list_email_distinct = ILE.iId_newsletterservice_import_list_email_distinct
WHERE ILE.iId_newsletterservice_import_list = 1000

SELECT I.iId_newsletterservice_import_list, 
        I.iId_newsletterservice_import_list_email, 
        BL.iId_purchased_email_list
FROM #tb1 AS BL WITH(NOLOCK)
    INNER JOIN #tb2 AS I WITH(NOLOCK) ON I.sEmail LIKE BL.sEmail

DROP TABLE #tb1
DROP TABLE #tb2

表格统计数据:

  • OtherDb.dbo.Purchased_Email_List:标记为bPattern = 1
  • 的13行,2行
  • OtherDb.dbo.Purchased_Email_List_Email:324289行,122行图案(在本期中使用)
  • dbo.NewsletterService_import_list_email:15.5M行
  • dbo.NewsletterService_import_list_email_distinct ~1.5M行
  • WHERE ILE.iId_newsletterservice_import_list = 1000检索~13k行

我可以根据要求发布有关表格的更多信息。

有人能帮我理解吗?

更新

以下是CTE查询的查询计划:

Execution plan with CTE

以下是包含临时表的查询计划:

Execution plan with temp tables

3 个答案:

答案 0 :(得分:3)

正如您在查询计划中所看到的,对于CTE,引擎保留将它们基本上作为查找应用的权限,即使您想要连接也是如此。

如果不确定它可以独立地运行整个事情,事先基本上生成一个临时表...让我们为每一行运行一次。

这非常适合他们可以像魔术一样进行的递归查询。

但是你看到 - 在嵌套的嵌套循环中 - 它可能会非常错误 你已经通过尝试真正的临时表来自己找到答案了。

答案 1 :(得分:3)

<强>并行即可。如果您在TEMP TABLE查询中注意到,则第3个查询在分发和收集第一个查询的工作时表示并行性。结合第一个和第二个查询的结果时的并行性。第一个查询也偶然相对成本为77%。因此, TEMP TABLE 示例中的查询引擎能够确定第一个查询可以从Parallelism中受益。特别是当并行性收集流和分发流时,它允许分割工作(连接),因为数据以允许分割工作然后重新组合的方式分发。请注意,第二个查询的成本是0%,因此您可以忽略它,除了需要合并之外没有任何成本。

查看CTE,这是完全处理的,而不是并行处理。所以对于CTE来说,它无法弄清楚第一个Query可以并行运行,以及第一个和第二个查询的关系。可能的是,对于多个CTE表达式,它假设有一些依赖性,并且没有足够的前瞻性。

您可以对CTE执行的另一项测试是保留 CTE_Patterns ,但将其作为“子查询派生”表放到CTE中的第3个查询中,以消除 CTE_Emails 。看到执行计划会很奇怪,看看是否存在并行性。

答案 2 :(得分:1)

根据我的经验,当您需要联接回数据时,最好将CTE用于递归和临时表。通常可以提高查询速度。