从聚集索引中找到每个项目最早日期的最佳方法是什么

Ell*_*eny 5 performance sql-server sql-server-2012 greatest-n-per-group query-performance

我有一个 SQL Server 2012 表,其中包含这样的列:

ID int NOT NULL,
EventDate datetime NOT NULL,
... 32 other columns...
Run Code Online (Sandbox Code Playgroud)

其中该表在大约 10000 个不同的 ID 值范围内有大约 50 亿行。

该表有一个唯一的聚集索引,如下所示:

CREATE UNIQUE CLUSTERED INDEX [MyIndex] ON [dbo].[MyTable] (
    [ID] ASC,
    [EventDate] ASC
)
Run Code Online (Sandbox Code Playgroud)

我需要找到最早的每个 ID 的 EventDate,我可以使用以下查询获得它:

SELECT ID, min(EventDate) FROM [dbo].[MyTable] GROUP BY ID
Run Code Online (Sandbox Code Playgroud)

但是,此查询只需不到 2 分钟即可完成。

由于 NDA 限制,我无法分享我正在查看的问题的细节(查询计划等),但我可以建议我看到的是聚集索引扫描,因此它正在检查表中的所有行。鉴于数据是按 EventDate 序列组织的,我希望检索速度会快得多,但我不太确定如何。任何其他特定于 ID 的范围查询都会在几毫秒内响应,并且该表最近已重建并重新编制索引,因此我认为没有任何统计更新会有所帮助。

任何人都可以建议一种更好的方法来确定避免扫描整个聚集索引的最小 per-ID EventDate 值吗?

我确实有一个包含(10,000)个不同id值的表格。

ype*_*eᵀᴹ 14

这与“索引跳过扫描”优化有关(请参阅下面的连接项,从 2011 年开始)。不幸的是,它已被关闭为“不会修复”

一些相关的增强已经存在,但仅适用于分区表:分区表和索引上的查询处理增强

但是存在各种解决方法:


解决方法/解决方案 1:CROSS APPLY使用子查询TOP 1

如果有一个包含 (10K) 个不同ID值的表,我们可以用它来做一个CROSS APPLY.

-- if you don't have a table already
CREATE TABLE MyTableIDs
  ( ID int NOT NULL,
    PRIMARY KEY (id)
  ) ;

-- we only do this once
INSERT INTO MyTableIDs (ID)
SELECT ID 
FROM MyTable
GROUP BY ID ;
Run Code Online (Sandbox Code Playgroud)

然后查询将执行 (10K) 搜索索引:

SELECT  i.ID, a.EventDate
FROM    MyTableIDs AS i
    CROSS APPLY
        ( SELECT TOP (1) t.EventDate
          FROM     MyTable AS t
          WHERE    t.ID = i.ID
          ORDER BY EventDate
        ) AS a ;
Run Code Online (Sandbox Code Playgroud)

变通方法/解决方案 2:使用递归 CTE 实现“跳过扫描”

另一种选择是递归 CTE 来实现跳过扫描。这是@PaulWhite 于 2010 年 10 月在 SSC 上发布的演示脚本,演示了 rCTE 的速度有多快:计算兴趣查询

在你的情况下,它会是这样的:

WITH    RecursiveCTE
AS      (
        SELECT  a.*
        FROM    ( SELECT TOP (1)  ID, EventDate
                  FROM     MyTable
                  ORDER BY ID, EventDATE
                ) AS a
        UNION   ALL
        SELECT  r.ID, r.EventDate
        FROM    (
                -- A cunning way to use TOP in the recursive part of a CTE ;)
                SELECT  t.ID, t.EventDate,
                        rn = ROW_NUMBER() OVER (ORDER BY t.EventDate)
                FROM    MyTable AS t
                JOIN    RecursiveCTE AS r
                        ON  r.ID < t.ID
                ) AS r
        WHERE   r.rn = 1
        )
SELECT  *
FROM    RecursiveCTE
OPTION  (MAXRECURSION 0) ;
Run Code Online (Sandbox Code Playgroud)

变通方法/解决方案 3:分区表

如果您有企业版,则可用。链接文章中有关此可能性的更多详细信息:分区表和索引的查询处理增强

主要缺点是跳过扫描仅ID在分区列时才起作用。


变通方法/解决方案 4:附加索引

添加 NCI 索引(ID, EventDate)允许对更小的索引进行索引扫描。有关解释,请参阅@Daniel Hutmacher 的回答

它仍然是一个扫描,而不是(很多)搜索,所以我不确定它是否会像选项 1 和 2 一样好。当然,一切都取决于细节(列大小、不同 ID 的数量与重复值的数量等) )。


解决方法/解决方案(不,不起作用)5:索引视图

好主意,如果它奏效的话。不幸的是,如果视图具有GROUP BYMIN/MAX,则无法为视图编制索引,请参阅索引视图的限制。

我想知道为什么,因为它被允许(并被索引)如果它有GROUP BYCOUNT_BIG()。也可能有一个关于它的 Connect 项目!


变通方法/解决方案 6:“自己动手”索引视图

自己实现一个相当于索引视图的方法。例如,您可以MinEventDateMyTableIDs(具有不同ID值的表,请参阅选项 1)中添加一列,并在MyTable其中添加触发器,相应地更新此列。那么您的查询将是一个简单的SELECTfrom MyTableIDs


相关网页的链接


Dan*_*her 5

您可以尝试添加具有相同定义的非聚集索引,这将使索引的大小显着减小(特别是如果您的表很宽)。

您不会消除扫描,但它会更快。

聚集索引包含整个表的数据,而非聚集索引仅存储您定义的特定列(以及指向聚集键或行 ID 的指针,但在您的情况下,它们已经在索引键中)。

  • 至少对于 SQL Server 而言,情况正好相反。从这个意义上说,“集群”意味着“交错”。 (4认同)
  • @Elliveny 那么你基本上只剩下看更多的内存或更快的存储 imo。:) (2认同)