为啥查询优化器完全忽略索引视图索引?
Posted
技术标签:
【中文标题】为啥查询优化器完全忽略索引视图索引?【英文标题】:Why Query Optimizer totally ignores indexed view indexes?为什么查询优化器完全忽略索引视图索引? 【发布时间】:2014-03-31 02:38:40 【问题描述】: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 sn-p 来试验索引公式,但可能我应该做更多的事情:以某种方式调整 statistcs 吗?
【问题讨论】:
你有标准版 SQL Server 吗? SQL Fiddle 不是。如果你指定WITH (NOEXPAND)
它使用索引就好了。
这样的索引(实际上只是重复表)首先真的没有多大意义......你基本上只是在浪费(1 ) 通过复制数据来获得磁盘空间,以及 (2) 每次更新基表时都必须维护那些 ...$ALL
索引 ....
您确实需要确保您创建的每个索引都会产生相关成本。然后在使用这些索引运行的查询的性能增益与维护和更新这些索引所需的时间之间进行平衡。仅仅因为您可以创建索引并不意味着您应该这样做。
Features Supported by the Editions of SQL Server 2012。参见Automatic use of indexed view by query optimizer
(与Direct use
相比,它指定需要WITH (NOEXPAND)
)
【参考方案1】:
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 个外键引用,这可能不是您想要的。)
【讨论】:
感谢您发现错误!有趣的是,当 Martin 扩大页数时,我制作了沉重的 SELECT * 部分以尝试强制 QO 使用索引视图......我将继续实验。【参考方案2】:在 2012 Developer Edition 上运行,未提示查询的成本大约是提示查询的 8 倍
虽然 8 倍可能听起来很多,但您的示例数据非常小,直接从基表中选择的成本是 0.0267122
与 0.003293
的估计成本。
Paul White 在his answer here 中解释说,如果首先找到足够低的计划,甚至不会考虑自动索引视图匹配。
人为地提高所有相关表格的成本
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)
【讨论】:
谢谢!我可以再问一个问题:ROWCOUNT 和 PAGECOUNT 的值存储在哪里以及如何获取它们的值? dbcc show_statistics 需要静态名称,所以我不明白要使用哪个名称.. dbcc show_statistics ('t.Entity','PK_Entity') 不会返回这样的数字... 再问:“基表计划”是什么对象,存放在哪里? @RomanPokrovskij - 它更新了从SELECT data_pages,rows FROM sys.allocation_units au JOIN sys.partitions p on au.container_id = p.hobt_id WHERE p.object_id=object_id('Classificator1')
暴露给我们的列
@RomanPokrovskij - 这只是增加一些运营商成本的一种快速而肮脏的方式。它与实际加载大量测试数据不同,并且可能表现不同。请允许我以一种简单的方式说明 Paul White 关于基于成本的观点。是的,看看计划的根源。您可能应该只是让所有涉及的表更大(当然,这个答案中的假行数完全是多余的。也许尝试将它们的大小加倍等等)
@RomanPokrovskij - 虽然我也明白 Steve 的观点,即这种类型的结构让优化器变得非常困难。因此,也许您会发现似乎永远不会匹配的查询。【参考方案3】:
如果您使用的是 SQL Server Enterprise,请使用 WITH (NOExpand) 提示
您的查询将是SELECT * FROM EntityView with (noexpand)
【讨论】:
如果他们使用Enterprise
(或Developer
),他们不应该需要使用WITH (NOEXPAND)
提示
不同的是,没有提示的企业版可能决定不使用索引视图,而是使用基表。
但是如果你想强制它使用索引视图,你总是需要使用WITH (NOEXPAND)
——这不是他们使用企业版的条件。在较低版本中,它可以仅在应用此提示的情况下使用索引。
那么也可能是优化器达到了临界点。将 FORCESEEK 与 NOEXPAND 提示结合使用
这是一种有效的解决方法,但不能解决更深层次的问题。为什么视图匹配不起作用?如何启用?以上是关于为啥查询优化器完全忽略索引视图索引?的主要内容,如果未能解决你的问题,请参考以下文章