当我有索引时为什么要进行排序?

Gee*_*zer 8 sql-server optimization execution-plan azure-sql-database query-performance

Azure SQL 数据库。

我有一个表,我需要从中获取Col1Col2基于 的第一行和最新行CreateDate

CREATE TABLE dbo.table1 (
    Id            INT    IDENTITY(1,1) PRIMARY KEY ,
    Col1        VARCHAR(255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL ,
    Col2        VARCHAR(255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL ,
    CreateDate    DATETIME NOT NULL
) ;
Run Code Online (Sandbox Code Playgroud)

我有一个像这样的索引:

CREATE INDEX IX__table1_ASC
ON dbo.table1 (Col1, Col2, CreateDate );
Run Code Online (Sandbox Code Playgroud)

我获取第一行的查询是(计划在这里):

--Get the first row
SELECT    TOP (1) WITH TIES
        *
FROM    table1
ORDER BY ROW_NUMBER()
        OVER (PARTITION BY Col1, Col2
              ORDER BY CreateDate );
Run Code Online (Sandbox Code Playgroud)

索引扫描使用我创建的索引 ( IX__table1_ASC),但为什么我得到了排序?

在此输入图像描述

我的查询获取最新行(计划在这里):

--get latest row
SELECT    TOP (1) WITH TIES
        *
FROM    table1
ORDER BY ROW_NUMBER()
        OVER (PARTITION BY Col1, Col2
              ORDER BY CreateDate DESC); --desc here
Run Code Online (Sandbox Code Playgroud)

同样,索引扫描使用索引 ( IX__table1_ASC),但这次我得到两种排序。索引扫描后的第一个。难道优化器不够聪明,能够以相反的顺序读取索引吗?再说一遍,第二种是做什么用的?

实际的表相当大,因此您可以想象排序的成本很高。我怎样才能最好地优化这里?

在此输入图像描述

Mar*_*ith 17

索引扫描使用我创建的索引 (IX__table1_ASC),但为什么我得到排序?

因为您使用的是一种低效的方式来选择每组的顶行。

只需使用

WITH T
     AS (SELECT *,
                ROW_NUMBER()
                  OVER (
                    PARTITION BY Col1, Col2
                    ORDER BY CreateDate ) AS RN
         FROM   table1)
SELECT *
FROM   T
WHERE  RN = 1 
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

这里TOP (1) WITH TIES只是一种更加混乱且效率较低的选择行号等于 1 的所有行的方法。不幸的是,StackOverflow 上的一些回答者使用这种方法没有什么充分的理由,我可以看出除了喜欢新奇之外。

在您的第一个执行计划中,排序不是用来计算行号的,而是在没有排序的情况下计算出行编号的结果后对行进行排序的。

关于您的第二个查询,这是一个长期存在的优化器限制 - 您可以获得向后排序的索引扫描,并且不进行排序,如下所示。

WITH T
     AS (SELECT *,
                ROW_NUMBER()
                  OVER (
                    PARTITION BY Col1, Col2
                    ORDER BY CreateDate DESC ) AS RN
         FROM   table1
         ORDER BY Col1 DESC, Col2 DESC, CreateDate DESC
         OFFSET 0 ROWS
         )
SELECT *
FROM   T
WHERE  RN = 1 
Run Code Online (Sandbox Code Playgroud)

OFFSET 0 ROWS是一种允许ORDER BY在派生表中使用的 hack,这在 SQL Server 中通常是不允许的。重要的是给优化器一个单独的理由来考虑最佳排序。

演示级别ORDER BY可以达到相同的目的,但我更喜欢将 hack 放在更接近需要它的地方。此方法还允许您指定不同的呈现顺序。请记住,OFFSET 0有一天可能会被优化掉,就像TOP (100) PERCENT现在一样。


在 SQL Server 2000 中,有些人过去通过添加TOP 100 PERCENT ... ORDER BY. 至少在大多数情况下,这样做的效果是,只需SELECT从视图中执行简单操作,而无需ORDER BY在外部查询上执行任何操作,就会按所需的顺序返回行。这从来没有得到保证,在 SQL Server 2005 中,逻辑被添加到优化器中,TOP 100 PERCENT在这种情况下,优化器在逻辑上是冗余的。未来可能会发生同样的情况,因为OFFSET 0 ROWS它同样是多余的。

就我个人而言,我希望任何多余的工程努力都可以用于改进优化,因此这种黑客攻击并不是首先必要的!