Sql server 通过CTE嵌套时案例的性能

Sql server 通过CTE嵌套时案例的性能,sql-server,tsql,sql-server-2008-r2,database-performance,sql-execution-plan,Sql Server,Tsql,Sql Server 2008 R2,Database Performance,Sql Execution Plan,我有一个性能问题,我想我可能已经解决了,但我需要帮助理解为什么可能的解决方案可以改善SQL Server的行为,最重要的是,它是否可靠,例如,不可能随着数据的增长和更改而突然变慢,或者通过简单的代码更改。我也愿意接受更好的解决方案。这里有很多上下文,所以请耐心听我说。我一直在针对SQLServer2008R2进行开发和测试 我正在开发一个系统,它可以检查数据的某些条件,并根据这些条件在后台自动执行不同的操作。这些相同的条件用于向用户显示信息,例如dbo.Cases中给定条目的下一个操作何时发生,

我有一个性能问题,我想我可能已经解决了,但我需要帮助理解为什么可能的解决方案可以改善SQL Server的行为,最重要的是,它是否可靠,例如,不可能随着数据的增长和更改而突然变慢,或者通过简单的代码更改。我也愿意接受更好的解决方案。这里有很多上下文,所以请耐心听我说。我一直在针对SQLServer2008R2进行开发和测试

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

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

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

下面是视图的基本情况:

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

在上述实现中,轮询过程的查询运行得非常慢。查看执行计划,所发生的是dbo中每一行的汇总列的所有数据。每个轮询过程特有的其他条件没有处理得那么糟糕

我发现的潜在解决方案是从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的CASE WHEN语句的内容作为查找和扫描的谓词,从而大大限制了它检查的行数

不过,这种实现确实需要付出代价:用户界面必须左键与dbo.vwDataExtended连接新视图,以实现与原始dbo.vwDataExtended等效的功能。这是非常低效的,并且会导致一些相当繁忙的执行计划,尽管执行时间可能仍然可以接受,即使只是勉强可以接受


回到最初的问题:为什么这会如此改善SQL Server的行为?是否有任何文件可用于解释差异?除了这种设计,是否有一种替代方案不涉及到处复制逻辑?

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

您正在使用CTE根据之前的一些计算进行添加,但是还有两种其他技术可以实现这一点

/* 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)
这两种技术都使您能够通过引用给定的列别名来引用以前的一些计算

我会尝试使用outerapply来进行那些您希望通过别名引用的计算,然后我还会将整个视图视为一个派生表,从而消除对CTE的所有需要。很明显,其中一些只是猜测什么最适合,所以继续使用执行计划进行进一步调查

e、 g


IIRC CTE一经访问即不存储,每次使用时必须重新评估。在此示例中,数据CTE被引用了3次。如果将该核心CTE设置为写入表变量,并使用该变量,则只需处理一次。CTE在一个从另一个选择的链中使用,直到视图在底部选择为止,因此只能使用一次。我从未在执行计划中看到任何表明正在发生多个使用的情况。如果这是真的,则此查询将在两列中返回相同的guid:;使用cte1作为select id=newid,cte2作为cte1中的select id选择cte1.id,cte1中的cte2.id,cte2我的视图所做的更多是这样的:;使用cte1作为select id=newid,cte2作为cte1中的select id select cte2.id from cte2 using APPLY,在将一些列转换为使用它之后似乎很有希望,但当我转换其余列时,SQL Server停止使用包含的比较作为谓词。因此,它不使用索引来扫描SCODE='O'或SCODE='R',而是读取数据库中的所有行
o、 实例,计算表达式的值,然后由表达式进行过滤,但不是在读取一组其他联接表之前。。。。添加IsSubmittable和IsAutomaticallySubmitable,因为列被证明更具灾难性/恐怕通过这些片段帮不上什么忙。我怀疑这可能需要反复试验,你可能会有选择地混合使用这两种技术。注意,我们对您的表/列/索引或其中的数据一无所知。但我不会尝试CTEs,因为你通过这个问题解释了什么。我不想把它变成一个20页的练习:P它已经比一般的SO问题长了很多,而且它比我必须在sqlfiddle上放置完整的模式要长得多。我认为部分问题在于行数估计值不好,但不知道如何改进它们;半随机地抛出统计数据和/或索引是没有帮助的。我已经能够使用应用程序来告诉SQL Server嘿,不要使用此专栏进行查找,但这感觉非常粗糙,而且可能不可靠。使用应用程序不是一种黑客行为,它可以非常有效-祝你好运
/* 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)
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