通过CTE嵌套时的表现

时间:2015-10-19 22:23:21

标签: sql-server tsql sql-server-2008-r2 database-performance sql-execution-plan

我有一个性能问题,我认为我可能已经解决了,但我需要帮助理解为什么可能的解决方案可以改善SQL Server的行为,最重要的是,它是否可靠(例如,不太可能突然变慢)随着数据的增长和变化,或者简单的代码更改)。我也完全接受更好的解决方案。这里有很多背景,所以请耐心等待。我一直在针对SQL Server 2008 R2进行开发和测试。

我正在研究检查数据某些条件的系统,并根据这些条件,在后台自动执行不同的操作。这些相同的条件用于向用户显示信息,例如dbo.Cases中给定条目的下一个操作何时发生,或者为什么不执行任何操作。

有一个视图收集这些条件的所有数据,并使用CASE WHEN语句选择条件作为位标志。这些列可以由UI使用,也可以通过自动轮询过程在WHERE子句中使用。

条件通常以相同的方式使用,因此希望避免在任何地方重复它们。 "汇总栏"创建时检查其他条件:IsSubmittable和IsAutomaticallySubmittable。

这里基本上是视图的样子:

CREATE VIEW dbo.vwDataExtended
AS
WITH

Data AS (

SELECT      Cases.CNR,
            Cases.CNRLink,
            IsActiveCompany =
                CASE
                    WHEN Companies.IsActive IS NULL THEN CAST(0 as BIT)
                    ELSE Companies.IsActive
                END,
            IsOpen =
                CASE
                    WHEN Cases.SCODE = 'O' THEN CAST(1 as BIT)
                    ELSE CAST(0 as BIT)
                END,
            IsReopened =
                CASE
                    WHEN Cases.SCODE = 'R' THEN CAST(1 as BIT)
                    ELSE CAST(0 as BIT)
                END,
            IsCompanyClassCode =
                CASE
                    WHEN CompanyClassCode.CompanyClassCodeID IS NOT NULL THEN CAST(1 as BIT)
                    ELSE CAST(0 as BIT)
                END
            --several other conditions

FROM        dbo.Cases with (nolock)

LEFT JOIN   dbo.Companies with (nolock)
    ON      Companies.CompanyCode = Cases.CompanyCode
    AND     Companies.CustomerLevelTypeCode = 'CUSTOMER'

LEFT JOIN   dbo.ClassCodes with (nolock)
    ON      ClassCodes.ClassCode = Cases.ClassCode

--identify enabled company class codes
LEFT JOIN   dbo.ISO_Search_CompanyClassCode CompanyClassCode with (nolock)
    ON      CompanyClassCode.CompanyCode = Cases.CompanyCode
    AND     CompanyClassCode.ClassCode = ClassCodes.ClassCode
    AND     CompanyClassCode.EnableOn <= GETDATE()

--lots of other joins

) --end CTE Data

AddIsSubmittable as (
    select      *,
                IsSubmittable = case when

                    IsActiveCompany = 1
                    and     IsCompanyClassCode = 1
                    --7 other similar conditions

                    then cast(1 as bit)
                    else cast(0 as bit)
                end
    from        Data
), --end CTE AddIsSubmittable

AddIsAutomaticallySubmittable as (
    select      *,
                IsAutomaticallySubmittable = case when

                    IsSubmittable = 1
                    and     (IsOpen = 1 OR IsReopened = 1)
                    --2 other similar conditions

                    then cast(1 as bit)
                    else cast(0 as bit)
                end
    from        AddIsSubmittable
) --end CTE AddIsAutomaticallySubmittable

select      CNR,
            CNRLINK,
            IsActiveCompany,
            IsOpen,
            IsReopened,
            IsCompanyClassCode,
            IsISOSubmittable,
            IsAutomaticallySubmittable

from        AddIsAutomaticallySubmittable

以下是自动轮询过程如何使用它的示例:

DECLARE CURSOR_NEW CURSOR LOCAL FAST_FORWARD FOR
    SELECT      DISTINCT
                d.CNR

    FROM        dbo.vwDataExtended d with(nolock)

    WHERE       d.IsAutomaticallySubmittable = 1
    AND         --process-specific conditions

从概念上讲,这种设计很好,因为它确保UI代码始终与自动化流程的代码同步。但是,它要求视图非常有效,因为其中一些进程运行相当频繁(主要运行间隔为10分钟),dbo.Cases中有近300万行。

通过上述实现,轮询进程的查询运行速度非常慢。查看执行计划,会收集dbo.Cases中每行的汇总列的所有数据,然后将其过滤到接近结尾。其他特定于每个投票过程的条件都没有得到很好的处理。

我找到的潜在解决方案是从vwDataExtended中删除汇总列,并将每个列作为单独的视图添加其条件在WHERE子句中,如下所示:

create view dbo.vwDataExtended_IsSubmittable
as

select  CNR,
        CNRLINK,
        IsActiveCompany,
        IsOpen,
        IsReopened,
        IsCompanyClassCode

from    dbo.vwDataExtended with(nolock)

where   IsActiveCompany = 1
and     IsCompanyClassCode = 1
--7 other similar conditions
go

create view dbo.vwDataExtended_IsAutomaticallySubmittable
as
select  CNR,
        CNRLINK,
        IsActiveCompany,
        IsOpen,
        IsReopened,
        IsCompanyClassCode

from    dbo.vwDataExtended_IsSubmittable with(nolock)

where   (IsOpen = 1 OR IsReopened = 1)
--2 other similar conditions

然后修改了轮询过程的查询:

DECLARE CURSOR_NEW CURSOR LOCAL FAST_FORWARD FOR
    SELECT      DISTINCT
                d.CNR

    FROM        dbo.vwDataExtended_IsAutomaticallySubmittable d with(nolock)

    WHERE       --process-specific conditions

为此实现生成的执行计划得到了大幅改进,表明SQL Server正在使用vwDataExtended的{​​{1}}语句的内容作为查找和扫描的谓词,从而极大地限制了它检查的行数。

此实施确实带来了成本:用户必须CASE WHEN LEFT JOIN新视图才能获得与原始dbo.vwDataExtended相同的功能。这是非常低效的,并且使得一些相当繁忙的执行计划,尽管执行时间可能仍然可以接受(如果只是勉强)。

回到最初的问题:为什么这会如此改善SQL Server的行为?有没有可用于解释差异的文档?是否有一种替代这种设计的方法并不涉及到整个地方复制逻辑?

1 个答案:

答案 0 :(得分:0)

我避免使用CTE,除非需要递归,或者CTE可以在最终查询中重用。这两个条件都不适用,因此我根本不会使用CTE。

您正在使用CTE来添加&#34;基于一些先前的计算,但还有其他两种技术。

/* a simple "derived table" (or "inline view") */

    select ..., comp.IsActiveCompany
    from (
           select *, IsActiveCompany =
               case when Companies.IsActive IS NULL THEN CAST(0 as BIT)
                                ELSE Companies.IsActive END
         ) comp

/* using the apply operator for the calculation */
.
    select ..., ca1.IsActiveCompany
    from dbo.Companies
        OUTER APPLY (
             SELECT 
                    CASE
                        WHEN Companies.IsActive IS NULL THEN CAST(0 as BIT)
                        ELSE Companies.IsActive
                    END
            ) as ca1 (IsActiveCompany)

这两种技术都使您能够通过引用给定的列别名来引用某些先前的计算。

我很想尝试将OUTER APPLY用于那些你想通过别名引用的计算,然后我也会把整个视图当作一个派生表来消除对CTE的所有需要​​。显然,其中一些是对最适合什么的猜测,因此继续使用执行计划进一步调查。

e.g。

select      CNR,
            CNRLINK,
            IsActiveCompany,
            IsOpen,
            IsReopened,
            IsCompanyClassCode,
            IsISOSubmittable,
            IsAutomaticallySubmittable
from (
        SELECT      Cases.CNR,
                    Cases.CNRLink,

                    CA1.IsActiveCompany,

                   IsSubmittable = case when

                            CA1.IsActiveCompany = 1
                            and     IsCompanyClassCode = 1
                            --7 other similar conditions

                            then cast(1 as bit)
                            else cast(0 as bit)
                        end

                   -- lots of stuff missing to put back in

        FROM        dbo.Cases with (nolock)

        LEFT JOIN   dbo.Companies with (nolock)
            ON      Companies.CompanyCode = Cases.CompanyCode
            AND     Companies.CustomerLevelTypeCode = 'CUSTOMER'
            OUTER APPLY (
                         SELECT 
                                CASE
                                    WHEN Companies.IsActive IS NULL THEN CAST(0 as BIT)
                                    ELSE Companies.IsActive
                                END
                        ) as ca1 (IsActiveCompany)

        LEFT JOIN   dbo.ClassCodes with (nolock)
            ON      ClassCodes.ClassCode = Cases.ClassCode

        --identify enabled company class codes
        LEFT JOIN   dbo.ISO_Search_CompanyClassCode CompanyClassCode with (nolock)
            ON      CompanyClassCode.CompanyCode = Cases.CompanyCode
            AND     CompanyClassCode.ClassCode = ClassCodes.ClassCode
            AND     CompanyClassCode.EnableOn <= GETDATE()

        --lots of other joins

    ) AS derived