2010-12-20 33 views
6

我发现,在具有适当索引的索引视图中,MAX(日期)正在执行整个索引扫描,然后是流聚合,而TOP(1)日期是最佳使用索引并仅扫描单个行。对于大量的行,这导致严重的性能问题。我已经包含了一些代码来演示下面的问题,但有兴趣知道其他人是否可以解释为什么会发生此行为(它不会发生在具有类似索引的表上)以及它是否是SQL Server优化程序中的错误(I已经在2008 SP2和R2上进行了测试,并且都显示相同的问题)。为什么MAX在索引视图上表现比TOP更差?

CREATE TABLE dbo.TableWithDate 
(
    id INT IDENTITY(1,1) PRIMARY KEY, 
    theDate DATE NOT NULL 
); 

CREATE NONCLUSTERED INDEX [ix_date] ON dbo.TableWithDate([theDate] DESC); 

INSERT INTO dbo.TableWithDate(theDate) VALUES('1 MAR 2010'),('1 MAR 2010'), ('3 JUN 2008'); 

-- Test 1: max vs top(1) on the table. They give same optimal plan (scan one row from the index, since index is in order) 
SELECT TOP(1) theDate FROM dbo.TableWithDate ORDER BY theDate DESC; 
SELECT MAX(theDate) FROM dbo.TableWithDate; 

CREATE TABLE dbo.TheJoinTable 
(
    identId INT IDENTITY(1,1) PRIMARY KEY, 
    foreignId INT NOT NULL, 
    someValue INT NOT NULL 
); 

CREATE NONCLUSTERED INDEX [ix_foreignValue] ON dbo.TheJoinTable([foreignId] ASC); 

INSERT INTO dbo.TheJoinTable(foreignId,someValue) VALUES (1,10),(1,20),(1,30),(2,5),(3,6),(3,10); 

GO 

CREATE VIEW dbo.TheTablesJoined 
WITH SCHEMABINDING 
AS 
    SELECT T2.identId, T1.id, T1.theDate, T2.someValue 
    FROM dbo.TableWithDate AS T1 
    INNER JOIN dbo.TheJoinTable AS T2 ON T2.foreignId=T1.id 
GO 

-- Notice the different plans: the TOP one does a scan of 1 row from each and joins 
-- The max one does a scan of the entire index and then does seek operations for each item (less efficient) 
SELECT TOP(1) theDate FROM dbo.TheTablesJoined ORDER BY theDate DESC; 

SELECT MAX(theDate) FROM dbo.TheTablesJoined; 

-- But what about if we put an index on the view? Does that make a difference? 
CREATE UNIQUE CLUSTERED INDEX [ix_clust1] ON dbo.TheTablesJoined([identId] ASC); 
CREATE NONCLUSTERED INDEX [ix_dateDesc] ON dbo.TheTablesJoined ([theDate] DESC); 

-- No!!!! We are still scanning the entire index (look at the actual number of rows) in the MAX case. 
SELECT TOP(1) theDate FROM dbo.TheTablesJoined ORDER BY theDate DESC; 

SELECT MAX(theDate) FROM dbo.TheTablesJoined; 
+2

这听起来更像是一个尚未在优化器中实现的功能,而不是bug。如果你使用'NOEXPAND'表格提示怎么办? – Gabe 2010-12-20 07:38:18

+0

出于好奇,如果在表之间声明外键会发生什么? – Ronnis 2010-12-20 14:34:51

+0

如果我添加一个外键约束(我应该提到我尝试过),这并没有什么区别。但是,将WITH(NOEXPAND)添加到索引视图的查询确实会强制它选择不同的(最优)计划。感谢您推荐我尝试。 – 2010-12-20 15:19:16

回答

1

要评估任何聚合函数的值(如max),必须读取表中的所有行,并且因为其中一个值用于评估。 前1只需要读取一行,这可以非常快速地完成 当它不是强制排序和没有适合的索引来扫描整个表。 在这些情况下,您可以创建一个合适的索引来提高性能。

+0

“前1名只需要阅读一行” - 你确定吗?如果我们有ORDER BY的TOP(99%的情况),我敢肯定引擎必须读取所有行来排序,然后执行TOP。或者我错了... – RPM1984 2010-12-20 07:26:54

+4

RPM1984:优化器知道看到'ORDER BY'子句匹配索引的顺序,因此它知道它不必排序,这意味着它不必读取所有行。 – Gabe 2010-12-20 07:39:14

+0

@RPM 99%非常好。我经常看到Top在没有(错误)命令的情况下使用。但在你的情况下,只需添加所需的索引。 – 2010-12-20 08:05:26

2

John Sansom coveredMAXTOP的性能特征,然而他的结果并没有具体回答你的问题。

我想答案就在于这样一个事实:MAX朝向捣鼓页和数据,其中TOP是仅朝向限制行被获取的数量面向运营商的页面面向通用聚合函数。

在这个狭义用例中,两个示例查询都能够遵循相同的事情,并且可以返回相同的结果。使用TOP的查询受益于在该用例中使用该方法所提供的特定优化。

我甩了两个查询的XML计划,并使用MAX语句包含:

<DefinedValues> 
    <DefinedValue> 
    <ColumnReference Column="Expr1004" /> 
    <ScalarOperator ScalarString="MAX([db].[dbo].[TheTablesJoined].[theDate])"> 
     <Aggregate AggType="MAX" Distinct="false"> 
     <ScalarOperator> 
      <Identifier> 
      <ColumnReference Database="[db]" Schema="[dbo]" Table="[TheTablesJoined]" Column="theDate" /> 
      </Identifier> 
     </ScalarOperator> 
     </Aggregate> 
    </ScalarOperator> 
    </DefinedValue> 
</DefinedValues> 

使用TOP声明代替XML定义在MAX查询什么被聚合的包含在此:

<TopExpression> 
    <ScalarOperator ScalarString="(1)"> 
    <Const ConstValue="(1)" /> 
    </ScalarOperator> 
</TopExpression> 

使用TOP时,执行计划中的执行计划少了很多。

0

什么版本的SQL Server?只有企业和开发人员会自动使用索引视图,其他版本将扩展查询以针对底层表。

您想要指定NOEXPAND查询提示。请参阅How can i speed up this Indexed View?的解答

相关问题