11

SQL Fiddle:http ://sqlfiddle.com/#!6/d4496/ 1(为您的实验预先生成数据)

有明显的表:

CREATE TABLE Entity 
(
  ID int,
  Classificator1ID int,
  Classificator2ID int,
  Classificator3ID int,
  Classificator4ID int,
  Classificator5ID int
);

和观点:

CREATE VIEW dbo.EntityView (ID, Code1, Code2, Code3, Code4, Code5) 
WITH SCHEMABINDING

其中实体字段 Classificator1ID..Classificator5ID 解析为分类器值 Code1..Code5

这个视图上有很多索引:

CREATE UNIQUE CLUSTERED INDEX [IXUC_EntityView$ID] ON EntityView
  ([ID]);
CREATE UNIQUE NONCLUSTERED  INDEX [IXU_EntityView$ID$include$ALL] ON EntityView
  ([ID]) INCLUDE (Code1, Code2, Code3, Code4,  Code5);
CREATE UNIQUE NONCLUSTERED  INDEX [IXU_EntityView$ALL] ON EntityView
  ([ID],Code1, Code2, Code3, Code4,  Code5);  
CREATE UNIQUE NONCLUSTERED  INDEX [IXU_EntityView$ID$Code1] ON EntityView
  ([ID],Code1);
CREATE UNIQUE NONCLUSTERED  INDEX [IXU_EntityView$ID$include$Code1] ON EntityView
  ([ID])INCLUDE (Code1);
CREATE NONCLUSTERED  INDEX [IX_EntityView$Code1] ON EntityView
  (Code1);
CREATE NONCLUSTERED  INDEX [IX_EntityView$Code1$include$ID] ON EntityView
  (Code1) INCLUDE (ID);

但 QO 从不使用它们!尝试这个:

SELECT * FROM EntityView;

SELECT ID, Code1 FROM EntityView;

SELECT ID, Code1, Code2, Code3, Code4, Code5 FROM EntityView;

SELECT ID, Code1, Code2, Code3, Code4, Code5 FROM EntityView WHERE ID=1;

SELECT ID, Code1 FROM EntityView Where Code1 like 'NR%';

为什么?尤其是“包含”索引有什么问题?已创建索引,具有所有字段但仍未使用...

补充:这只是测试!请不要这么生气,也不要逼我分析那些索引维护问题。

在我的真实项目中,我无法解释为什么 QO 会忽略索引视图(非常有用的索引视图)。但有时我看到它在其他地方使用它们。我创建了这个 db 片段来试验索引公式,但可能我应该做更多的事情:以某种方式调整 statistcs 吗?

4

3 回答 3

5

tl;dr 回答:如果您不指定 NOEXPAND,查询优化器不知道您正在从视图中提交一个简单的选择。它必须将查询的扩展(这是它所看到的)与一些视图索引相匹配。当它是与一堆演员的五向连接时,可能不会打扰。

查看与查询匹配的索引是一个难题,我相信您的视图对于查询引擎而言太复杂而无法匹配索引。考虑一下您的查询之一:

SELECT ID, Code1 FROM EntityView Where Code1 > 'NR%';

很明显,这可以使用视图索引,但这不是查询引擎看到的查询。如果您不指定 NOEXPAND,视图会自动展开,所以这就是查询引擎的内容:

SELECT ID, Code1 FROM (
    SELECT e.ID, 'NR'+CAST(c1.CODE as nvarchar(11)) as Code1, 'NR'+CAST(c2.CODE as nvarchar(11)) as Code2, 'NR'+CAST(c3.CODE as nvarchar(11)) as Code3, 'NR'+CAST(c4.CODE as nvarchar(11)) as Code4, 'NR'+CAST(c5.CODE as nvarchar(11)) as Code5
    FROM dbo.Entity e
        inner join  dbo.Classificator1 c1 on e.ID = c1.ID
        inner join  dbo.Classificator2 c2 on e.ID = c2.ID
        inner join  dbo.Classificator3 c3 on e.ID = c3.ID
        inner join  dbo.Classificator4 c4 on e.ID = c4.ID
        inner join  dbo.Classificator5 c5 on e.ID = c5.ID;
) AS V;

查询引擎看到这个复杂的查询,它有描述已定义视图索引的信息(但可能不是视图定义的 SQL)。鉴于此查询和视图索引都具有多个连接和强制转换,匹配是一项艰巨的工作。

请记住,您知道此查询和视图索引中的连接和匹配是相同的,但查询处理器不知道这一点。它对待这个查询就像它加入了 Classificator3 的五个副本,或者如果其中一个列是 'NQ'+CAST(c2.CODE as varchar(12))。视图索引匹配器(假设它尝试匹配这个复杂的查询)必须将此查询的每个细节与相关表上的视图索引的细节相匹配。

查询引擎的第一个目标是找出一种有效执行查询的方法。它可能不是为了花费大量时间来尝试将五向连接和 CAST 的每个细节与视图索引匹配。

如果我不得不猜测,我怀疑视图索引匹配器看到查询的结果列甚至不是任何基础表的列(因为 CAST)并且根本不费心尝试任何事情。补充我错了。我刚刚尝试了 Martin 的更新统计信息以使查询变得昂贵的建议,并且在没有 NOEXPAND 的情况下为其中一些查询匹配了视图索引。视图匹配器比我想象的要聪明!所以问题是,如果成本非常高,视图匹配器可能会更努力地匹配复杂的查询。

使用 NOEXPAND 提示而不是期望查询引擎能够找出这里匹配的内容。NOEXPAND 绝对是您的朋友,因为这样查询引擎就可以看到

SELECT ID, Code1 FROM EntityView Where Code1 > 'NR%';

然后视图索引匹配器立即很明显有一个有用的索引。

(注意:您的 SQL Fiddle 代码具有对同一个表的所有 5 个外键引用,这可能不是您想要的。)

于 2014-02-28T03:01:36.197 回答
4

在 2012 Developer Edition 上运行,未提示查询的成本大约是提示查询的 8 倍

在此处输入图像描述

虽然 8 倍可能听起来很多,但您的示例数据非常小,直接从基表中选择的成本0.02671220.003293视图中的估计成本相比。

Paul White 在他的回答中解释说,如果首先找到足够低的计划,甚至不会考虑自动索引视图匹配。

人为地提高所有涉及的表的成本

UPDATE STATISTICS Classificator1 WITH ROWCOUNT = 60000000, PAGECOUNT = 10000000 
UPDATE STATISTICS Classificator2 WITH ROWCOUNT = 60000000, PAGECOUNT = 10000000 
UPDATE STATISTICS Classificator3 WITH ROWCOUNT = 60000000, PAGECOUNT = 10000000 
UPDATE STATISTICS Classificator4 WITH ROWCOUNT = 60000000, PAGECOUNT = 10000000 
UPDATE STATISTICS Classificator5 WITH ROWCOUNT = 60000000, PAGECOUNT = 10000000 
UPDATE STATISTICS Entity         WITH ROWCOUNT = 60000000, PAGECOUNT = 10000000 

将基表计划的成本增加到 29122.6

您现在应该看到正在匹配的视图(在 Enterprise/Developer/Evaluation 版本上),除非您另有明确提示。

SELECT * FROM EntityView;

SELECT * FROM EntityView OPTION (EXPAND VIEWS) 

在此处输入图像描述

于 2014-02-27T16:47:26.540 回答
2

如果您使用的是 SQL Server Enterprise,请使用 WITH (NOExpand) 提示

您的查询将是SELECT * FROM EntityView with (noexpand)

于 2014-02-27T15:08:49.517 回答