在 SQL Server 执行计划中优化 Table Spool

Posted

技术标签:

【中文标题】在 SQL Server 执行计划中优化 Table Spool【英文标题】:optimize Table Spool in SQL Server Execution plan 【发布时间】:2017-04-21 06:13:16 【问题描述】:

我有以下 sql 查询并尝试使用执行计划对其进行优化。在执行计划中,它说估计子树成本是 36.89。有几个表线轴(Eager Spool)。谁能帮我优化这个查询。提前致谢。

SELECT
    COUNT(DISTINCT bp.P_ID) AS total,
    COUNT(DISTINCT CASE WHEN bc.Description != 'S' THEN bp.P_ID END) AS m_count,
    COUNT(DISTINCT CASE WHEN bc.Description = 'S' THEN bp.P_ID END) AS s_count,
    COUNT(DISTINCT CASE WHEN bc.Description IS NULL THEN bp.P_ID END) AS n_count
FROM 
    progress_tbl AS progress
INNER JOIN  Person_tbl AS bp  ON bp.P_ID = progress.person_id
LEFT OUTER JOIN  Status_tbl AS bm  ON bm.MS_ID = bp.MembershipStatusID          
LEFT OUTER  JOIN  Membership_tbl AS m ON m.M_ID = bp.CurrentMembershipID
LEFT OUTER JOIN  Category_tbl AS bc ON bc.MC_ID = m.MembershipCategoryID
WHERE 
    logged_when BETWEEN '2017-01-01' AND '2017-01-31'

【问题讨论】:

我没有看到 b 别名。我也没有看到 SELECT 部分中使用的 b_p 别名。发布的查询有问题 我在发布之前编辑了查询。我会相应地改变它 由于您现在提供了一个有效的查询,请参阅我的编辑 【参考方案1】:

这是一种你可以使用的技术。

WITH T AS
(
SELECT DISTINCT CASE
                  WHEN bc.Description != 'S' THEN 'M'
                  WHEN bc.Description = 'S' THEN 'S'
                  WHEN bc.Description IS NULL THEN 'N'
                END AS type,
                bp.P_ID
FROM   progress_tbl AS progress
       INNER JOIN Person_tbl AS bp
         ON bp.P_ID = progress.person_id
       LEFT OUTER JOIN Status_tbl AS bm
         ON bm.MS_ID = bp.MembershipStatusID
       LEFT OUTER JOIN Membership_tbl AS m
         ON m.M_ID = bp.CurrentMembershipID
       LEFT OUTER JOIN Category_tbl AS bc
         ON bc.MC_ID = m.MembershipCategoryID
WHERE  logged_when BETWEEN '2017-01-01' AND '2017-01-31' 
)
SELECT COUNT(DISTINCT P_ID) AS total,
       COUNT(CASE WHEN type= 'M' THEN P_ID END) AS m_count,
       COUNT(CASE WHEN type= 'S' THEN P_ID END) AS s_count,
       COUNT(CASE WHEN type= 'N' THEN P_ID END) AS n_count
FROM T

我将通过一个更简单的例子来演示它。

假设您现有的查询是

SELECT
    COUNT(DISTINCT number) AS total,
    COUNT(DISTINCT CASE WHEN name != 'S' THEN number END) AS m_count,
    COUNT(DISTINCT CASE WHEN name = 'S' THEN number END) AS s_count,
    COUNT(DISTINCT CASE WHEN name IS NULL THEN number END) AS n_count
FROM master..spt_values;

你可以改写如下

WITH T AS
(
SELECT DISTINCT CASE
                  WHEN name != 'S'
                    THEN 'M'
                  WHEN name = 'S'
                    THEN 'S'
                  ELSE 'N'
                END AS type,
                number
FROM   master..spt_values 
)
SELECT COUNT(DISTINCT number) AS total,
       COUNT(CASE WHEN type= 'M' THEN number END) AS m_count,
       COUNT(CASE WHEN type= 'S' THEN number END) AS s_count,
       COUNT(CASE WHEN type= 'N' THEN number END) AS n_count
FROM T

请注意,重写的成本要低得多,而且计划要简单得多。

【讨论】:

感谢您的回复。试过你的,它将子树成本从 36.98 降低到 26.87。您认为我们无法进一步优化还是因为数据无法进一步降低子树成本? @KapilaPerera - 不能说,因为它取决于连接的执行计划和WHERE,而您显示的计划不包括这些。无论如何,那将是一个不同的问题。你在这里问的问题是关于摆脱线轴的。【参考方案2】:

如前所述,您的查询似乎存在一些拼写错误/复制粘贴问题。这让我们很难弄清楚发生了什么。

table-spools 可能是 CASE WHEN b.description etc... 构造中发生的事情。 MSSQL 首先创建一个包含所有结果值的(内存)表,然后通过COUNT(DISTINCT ...) 运算符对该表进行排序和流式传输。我认为您对此无能为力,因为工作需要在某个地方完成。

总之,一些言论和胡思乱想:

我猜logged_whenprogress_tbl 表中? 如果是这样,您真的需要 LEFT OUTER JOIN 所有其他表吗?据我所知,它们没有被使用? 您正在尝试计算与条件匹配的P_IDs 的数量,并且您想将该数量分配给b.Description 为“S”、其他内容或NULL 的那些。 为此,您可以将总数计算为 m_count、s_count 和 n_count 的总和。这将为您节省 1 个 COUNT() 操作,不确定它在更大的范围内是否有很大帮助,但我猜所有位都有帮助。

类似这样的:

;WITH counts AS (

                    SELECT
                        COUNT(DISTINCT CASE WHEN b.Description != 'S' THEN b_p.P_ID END) AS m_count,
                        COUNT(DISTINCT CASE WHEN b.Description = 'S' THEN b_p.P_ID END) AS s_count,
                        COUNT(DISTINCT CASE WHEN b.Description IS NULL THEN b_p.P_ID END) AS n_count
                    FROM 
                        progress_tbl AS progress
                    INNER JOIN  Person_tbl AS bp  ON bp.P_ID = progress.person_id
                    LEFT OUTER JOIN  Status_tbl AS bm  ON bm.MS_ID = bp.MembershipStatusID     -- really needed?
                    LEFT OUTER  JOIN  Membership_tbl AS m ON m.M_ID = bp.CurrentMembershipID   -- really needed?
                    LEFT OUTER JOIN  Category_tbl AS bc ON bc.MC_ID = m.MembershipCategoryID   -- really needed?
                    WHERE 
                        logged_when BETWEEN '2017-01-01' AND '2017-01-31' -- what table does logged_when column come from????

    )

SELECT total = m_count + s_count + n_count,
       *
  FROM counts

更新

注意:使用 Martin Smith 的答案/示例代码,我意识到total 不一定是其他字段的总和。可能是给定的P_ID 显示为不同的description,然后可能属于不同的类别。因此,根据您的数据,我的回答可能是完全错误的。

【讨论】:

进度表有记录的列时 bc.Description 在选择查询中使用,所以我需要使用 Category_tbl。要访问 Category_tbl,我需要其余的表才能加入。 我使用 LEFT OUTER JOIN 来获取 bc 中不匹配的行。Description == null 来获取非成员

以上是关于在 SQL Server 执行计划中优化 Table Spool的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server执行计划

引用:初探Sql Server 执行计划及Sql查询优化

SQL Server查询优化器执行计划“语句提前终止的原因:超时”

谈一谈SQL Server中的执行计划缓存(下)

优化实体框架生成的 SQL Server 执行计划

sql server 统计信息