0

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

我正在研究检查数据的某些条件的系统,并根据这些条件在后台自动执行不同的操作。这些相同的条件用于向用户显示信息,例如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的的内容语句作为查找和扫描的谓词,因此极大地限制了它检查的行数。

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

回到原来的问题:为什么这会提高SQL Server的行为呢?有没有可以用来解释差异的文档?有没有可能替代这种设计,不涉及到所有地方的逻辑重复?

+1

IIRC CTE一旦被访问就不存储,每次使用时都需要重新评估。在这个例子中,'Data' CTE被引用3次。如果您将该核心CTE设置为写入表变量,并使用它,则只需处理一次。 – Malk

+0

CTE在链中使用(一个选择从另一个直到视图的选择在底部),所以它只能使用一次。我从未在执行计划中看到任何暗示多种用途正在发生的事情。 – Taudris

+0

如果这是真的,这个查询将在两列中返回相同的guid:';与cte1作为(选择id = newid()),cte2作为(从cte1选择ID)从cte1选择cte1.id,cte2.id,cte2 ' – Malk

回答

0

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

您正在使用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) 

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

我会试图尝试使用OUTER APPLY进行那些您想通过别名引用的计算,然后我会将整个视图作为派生表去除所有CTE需求。很显然,这是对最适合什么的猜测,所以继续使用执行计划进一步调查。

例如

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 
+0

使用APPLY似乎很有前途转换一些列来使用它,但是当我转换其余的时候,SQL Server停止使用包含的比较作为谓词。因此,不是使用索引来扫描'SCODE ='O'或SCODE ='R',而是读取'dbo.Cases'中的所有行,计算表达式的值,然后通过表达式进行过滤在阅读一堆其他连接表之前)。添加IsSubmittable和IsAutomaticallySubmittable作为列证明更加灾难性。 :/ – Taudris

+0

恐怕不可能通过这些片段帮助很多人。我怀疑它可能会经历试验和错误,你可能会尝试选择性地使用两种技术的混合。请注意,我们知道关于表/列/索引或其中的数据的“不是草皮”。但是我不会为你通过这个问题解释的内容尝试CTE。 –

+0

我不想把它变成20页的练习:P它已经比平均SO问题长得多了,并且需要比我必须在sqlfiddle上放置完整模式更长的时间。我认为问题的一部分是估计行数不佳,但不知道如何改进它们;半随机投掷统计数据和/或索引对事物没有帮助。我已经能够使用APPLY作为告诉SQL Server的一种方式“嘿,不要使用这个列进行查找”,但是这感觉非常冒险并且可能不可靠。使用APPLY的 – Taudris