选择索引视图的聚集索引的因素有哪些?

EBa*_*arr 19 sql-server clustered-index materialized-view index-tuning

简而言之,
哪些因素会影响查询优化器对索引视图索引的选择?

对我来说,索引视图似乎违背了我对优化器如何选择索引的理解。我之前看过这个问题,但 OP 并没有得到很好的接受。 我真的在寻找 guideposts,但我会编造一个伪示例,然后发布带有大量 DDL、输出、示例的真实示例。

假设我使用的是 Enterprise 2008+,理解 with(noexpand)

伪示例

以这个伪示例为例:我创建了一个包含 22 个连接、17 个过滤器和一个跨越 1000 万行表的马戏团小马的视图。这种观点实现起来很昂贵(是的,大写 E)。我将对视图进行 SCHEMABIND 和索引。然后一个 SELECT a,b FROM AnIndexedView WHERE theClusterKeyField < 84. 在我不知道的优化器逻辑中,执行了底层连接。

结果:

  • 无提示:4825 次读取 720 行,47 个 cpu 超过 76 毫秒,估计子树成本为 0.30523。
  • 使用提示:17 次读取,720 行,4 毫秒内 15 个 cpu,估计子树成本为 0.007253

那么这里发生了什么?我已经在Enterprise 2008、2008-R2 和 2012 中尝试过。根据我能想到的每个指标,使用视图索引的效率要高得多。我没有参数嗅探问题或倾斜的数据,因为这是临时的。

一个真实(长)的例子

除非你是一个受虐狂,否则你可能不需要或不想阅读这部分。

是的
,企业

Microsoft SQL Server 2012 - 11.0.2100.60 (X64) 2012 年 2 月 10 日 19:39:15 版权所有 (c) Microsoft Corporation Enterprise Edition(64 位)在 Windows NT 6.2(Build 9200:)(Hypervisor)上

风景

CREATE VIEW dbo.TimelineMaterialized    WITH SCHEMABINDING
AS
SELECT  TM.TimelineID,
        TM.TimelineTypeID,
        TM.EmployeeID,
        TM.CreateUTC,
        CUL.CultureCode,
        CASE 
           WHEN TM.CustomerMessageID    > 0 THEN TM.CustomerMessageID
           WHEN TM.CustomerSessionID    > 0 THEN TM.CustomerSessionID
           WHEN TM.NewItemTagID         > 0 THEN TM.NewItemTagID
           WHEN TM.OutfitID             > 0 THEN TM.OutfitID
           WHEN TM.ProductTransactionID > 0 THEN TM.ProductTransactionID
           ELSE 0 END  As HrefId,
        CASE 
          WHEN TM.CustomerMessageID    > 0 THEN IsNull(C.Name, 'N/A')   
          WHEN TM.CustomerSessionID    > 0 THEN IsNull(C.Name, 'N/A')
          WHEN TM.NewItemTagID         > 0 THEN IsNull(NI.Title, 'N/A')
          WHEN TM.OutfitID             > 0 THEN IsNull(O.Name, 'N/A')
          WHEN TM.ProductTransactionID > 0 THEN IsNull(PT_PL.NameLocalized, 'N/A')
                 END as HrefText

FROM       dbo.Timeline TM
INNER JOIN dbo.CustomerSession    CS    ON TM.CustomerSessionID    = CS.CustomerSessionID
INNER JOIN dbo.CustomerMessage    CM    ON TM.CustomerMessageID    = CM.CustomerMessageID
INNER JOIN dbo.Outfit             O     ON PO.OutfitID             = O.OutfitID
INNER JOIN dbo.ProductTransaction PT    ON TM.ProductTransactionID = PT.ProductTransactionID
INNER JOIN dbo.Product            PT_P  ON PT.ProductID            = PT_P.ProductID
INNER JOIN dbo.ProductLang        PT_PL ON PT_P.ProductID          = PT_PL.ProductID
INNER JOIN dbo.Culture            CUL   ON PT_PL.CultureID         = CUL.CultureID
INNER JOIN dbo.NewsItemTag        NIT   ON TM.NewsItemTagID        = NIT.NewsItemTagID
INNER JOIN dbo.NewsItem           NI    ON NIT.NewsItemID          = NI.NewsItemID
INNER JOIN dbo.Customer           C     ON  C.CustomerID = CASE 
                                             WHEN TM.TimelineTypeID = 1 THEN CM.CustomerID 
                                             WHEN TM.TimelineTypeID = 5 THEN CS.CustomerID
                                             ELSE 0 END

WHERE        CUL.IsActive = 1
Run Code Online (Sandbox Code Playgroud)

聚集索引

CREATE UNIQUE CLUSTERED INDEX PK_TimelineMaterialized  ON 
                   TimelineMaterialized (EmployeeID, CreateUTC, CultureCode, TimelineID)
Run Code Online (Sandbox Code Playgroud)

测试 SQL

-- NO HINT - - -  - - -  - - -  - - -  - - - 
SELECT  *                 --yes yes, star is bad ...just a test example
FROM    TimelineMaterialized TM 
WHERE 
            TM.EmployeeID   = 2
        AND TM.CultureCode  = 'en-US'
        AND TM.CreateUTC    > '9/10/2012'
        AND TM.CreateUTC    < '9/11/2012'

-- WITH HINT - - -  - - -  - - -  - - -  - - - 
SELECT  *               
FROM    TimelineMaterialized TM with(noexpand)
WHERE 
            TM.EmployeeID   = 2
        AND TM.CultureCode  = 'en-US'
        AND TM.CreateUTC    > '9/10/2012'
        AND TM.CreateUTC    < '9/11/2012'
Run Code Online (Sandbox Code Playgroud)

结果 = 11 行输出

11 行输出 - 两个查询相同

Profiler 输出
前 4 行没有提示。底部 4 行正在使用提示。

探查器

SQLPlan 格式的两种执行计划的
GitHub Gist 执行计划

无提示执行计划——为什么不使用我给你 SQL 先生的聚集索引?它聚集在 3 个过滤器字段上。试试吧,你可能会喜欢。
无提示 - 庞大的执行计划

使用提示时的简单计划。

使用提示 - 简单的执行计划

Pau*_*ite 26

匹配索引视图是一项相对昂贵的操作*,因此优化器首先尝试其他快速简便的转换。如果这些碰巧产生了一个便宜的计划(在您的情况下为 0.05 个单位),优化会提前结束。赌注是持续优化会消耗比节省的更多时间。记住优化器的主要目标是快速制定一个“足够好”的计划。

在视图上使用聚集索引本身并不昂贵,但将逻辑查询树与潜在索引视图匹配的过程可能会很昂贵。正如我在另一个问题的评论中提到的,查询中的视图引用在优化之前被扩展,所以优化器不知道你首先针对视图编写了查询 - 它只看到扩展的树(好像视图已内联)。

“足够好的计划”意味着优化器找到了一个合适的计划并在探索阶段早期停止。“超时”意味着它超过了它在当前阶段开始时将自己设置为“预算”的优化步骤数。

预算是根据前一阶段找到的最佳计划的成本设置的。使用如此低成本的查询 (0.05),预算移动的数量将非常少,并且考虑到示例查询中涉及的连接数量(例如,有很多方法可以重新排列内部连接),定期转换会很快耗尽.

如果您有兴趣了解更多关于为什么索引视图匹配很昂贵,因此留给后期优化和/或只考虑更昂贵的查询的更多信息,这里(pdf) 和这里(citeseer )。

另一个相关因素是索引视图匹配在优化阶段 0(事务处理)中不可用。

进一步阅读:

索引视图和统计数据

* 仅适用于企业版(或同等版本)