【问题标题】:Why does MAX perform so much worse than TOP on an indexed view?为什么 MAX 在索引视图上的表现比 TOP 差这么多?
【发布时间】:2011-05-28 01:27:58
【问题描述】:

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

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;

【问题讨论】:

  • 这听起来更像是一个尚未在优化器中实现的功能,而不是一个错误。如果你使用NOEXPAND 表提示怎么办?
  • 只是出于好奇,如果在表之间声明外键会怎样?
  • 如果我添加外键约束没有区别(我应该提到我已经尝试过)。但是,将 WITH (NOEXPAND) 添加到索引视图的查询确实会强制它选择不同的(最佳)计划。感谢您推荐我尝试一下。

标签: sql tsql sql-server-2008


【解决方案1】:

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 的语句包含此内容,而不是定义MAX 查询中聚合内容的XML:

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

使用TOP 时,执行计划中发生的事情要少得多。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • "Top 1 只需读取一行" - 你确定吗?如果我们有一个带有 ORDER BY 的 TOP(99% 的情况),我很确定引擎必须读取所有行来对它们进行排序,然后执行 TOP。还是我错了……
    • RPM1984:优化器知道ORDER BY 子句与索引的顺序匹配,因此它知道它不必排序,这意味着它不必读取所有行。
    • @RPM 99% 非常好。我经常看到 Top 没有按顺序使用(错误)。但在您的情况下,只需添加所需的索引。
    【解决方案3】:

    什么版本的 SQL Server?只有 Enterprise 和 Developer 会自动使用索引视图,其他版本会扩展查询以针对基础表。

    您要指定 NOEXPAND 查询提示。查看How can i speed up this Indexed View?的答案

    【讨论】:

      猜你喜欢
      • 2011-01-09
      • 1970-01-01
      • 1970-01-01
      • 2011-06-11
      • 2023-04-08
      • 2021-12-27
      • 2021-12-17
      • 2011-01-28
      • 1970-01-01
      相关资源
      最近更新 更多