【问题标题】:Optimizing ROW_NUMBER() in SQL Server在 SQL Server 中优化 ROW_NUMBER()
【发布时间】:2011-02-26 22:55:38
【问题描述】:

我们有许多机器会不定期地将数据记录到数据库中。对于每条记录,我想获取 this 记录和 previous 记录之间的时间段。

我可以使用 ROW_NUMBER 执行此操作,如下所示:

WITH TempTable AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY Machine_ID ORDER BY Date_Time) AS Ordering
    FROM dbo.DataTable
)

SELECT [Current].*, Previous.Date_Time AS PreviousDateTime
FROM TempTable AS [Current]
INNER JOIN TempTable AS Previous 
    ON [Current].Machine_ID = Previous.Machine_ID
    AND Previous.Ordering = [Current].Ordering + 1

问题是,它真的很慢(在大约 10k 条目的表上几分钟)-我尝试在 Machine_ID 和 Date_Time 上创建单独的索引,以及单个连接索引,但没有任何帮助.

有没有办法重写这个查询以加快速度?

【问题讨论】:

    标签: sql-server sql-server-2005 tsql optimization query-optimization


    【解决方案1】:

    给定的 ROW_NUMBER() 分区和顺序需要(Machine_ID, Date_Time) 上的索引才能一次性满足:

    CREATE INDEX idxMachineIDDateTime ON DataTable (Machine_ID, Date_Time);
    

    Machine_ID 和 Date_Time 上的单独索引几乎没有帮助。

    【讨论】:

    • 正如我所说,我也创建了该索引,但它根本没有提高查询性能。
    • 那是因为你的 * 触发了索引的临界点。将其限制为仅需要的列并使用包括使非聚集索引覆盖。如果需要的列太多,则必须将其更改为聚集索引,后果自负。
    • 您似乎是正确的,删除​​ * 将查询时间减少到几秒钟。我无法想象为什么会发生这种情况 - 您能否提供任何关于索引临界点是什么的链接?
    • 现在您知道 select * 不应出现在生产查询中的原因之一。另外,您有连接,因此您根据定义返回了不必要的列。
    • +1 尽管由于查询结构的原因,该索引可能对提问者的方案没有帮助,但总体上它确实提高了 OVER 子句的性能。
    【解决方案2】:

    与这个版本相比如何?:

    SELECT x.*
        ,(SELECT MAX(Date_Time)
          FROM dbo.DataTable
          WHERE Machine_ID = x.Machine_ID
              AND Date_Time < x.Date_Time
        ) AS PreviousDateTime
    FROM dbo.DataTable AS x
    

    还是这个版本?:

    SELECT x.*
        ,triang_join.PreviousDateTime
    FROM dbo.DataTable AS x
    INNER JOIN (
        SELECT l.Machine_ID, l.Date_Time, MAX(r.Date_Time) AS PreviousDateTime
        FROM dbo.DataTable AS l
        LEFT JOIN dbo.DataTable AS r
        ON l.Machine_ID = r.Machine_ID
            AND l.Date_Time > r.Date_Time
        GROUP BY l.Machine_ID, l.Date_Time
    ) AS triang_join
    ON triang_join.Machine_ID = x.Machine_ID
        AND triang_join.Date_Time = x.Date_Time
    

    如果使用 Machine_ID、Date_Time 上的索引,两者都会表现最佳,为了获得正确的结果,我假设这是唯一的。

    您还没有提到 * 中隐藏的内容,这有时意味着很多,因为 Machine_ID、Date_Time 索引通常不会覆盖,如果您有很多列或者它们有很多数据,. ..

    【讨论】:

    • 第二个查询在几秒钟而不是几分钟内完成,但第一个查询的执行速度比我能计时的要快。完美 - 谢谢!
    【解决方案3】:

    如果 dbo.DataTable 中的行数很大,那么您可能会遇到问题,因为 CTE 自加入自身。有一篇博文详细解释了这个问题here

    在这种情况下,我有时会创建一个临时表,将 CTE 查询的结果插入到该临时表中,然后针对该临时表进行连接(尽管这通常用于大量连接临时表的情况)表是必需的 - 在单个连接的情况下,性能差异将不太明显)

    【讨论】:

    • 我支持这种方法。 CTE 只是内联重写。就像重复自己的代码和自连接一样,没有什么可以保证优化器会将其假脱机到临时表中。如果你把东西放在你自己的表中,你可以选择索引和/或避免重复工作。话虽如此,我确实在代码维护很重要并且架构很容易更改的地方(或在视图中,例如这种情况)使用 CTE。
    【解决方案4】:

    我在 SQL Server 2005 中使用 CTE 时遇到了一些奇怪的性能问题。在许多情况下,用真正的临时表替换 CTE 可以解决问题。

    在进一步使用 CTE 之前,我会先尝试一下。

    对于我所看到的性能问题,我从来没有找到任何解释,而且真的没有时间去挖掘根本原因。但是我一直怀疑引擎无法像优化临时表一样优化 CTE(如果需要更多优化,可以对其进行索引)。

    更新

    在您评论这是一个视图之后,我将首先使用临时表测试查询,看看它是否表现更好。

    如果是这样,并且不能选择使用存储过程,您可以考虑将当前 CTE 设置为索引/物化视图。在走这条路之前,您需要先阅读该主题,因为这是否是一个好主意取决于很多因素,其中最重要的是数据的更新频率。

    【讨论】:

    • 我该怎么做?我是否需要用 Sproc 替换视图(因为视图不能有变量)?
    • 是的,我不清楚这是您问题的观点。查看我的答案的更新(将在几分钟后跟进)。
    【解决方案5】:

    如果您使用触发器存储最后一个时间戳并每次减去以获取差值怎么办?

    【讨论】:

    • 很遗憾,这是历史数据,并不总是按顺序添加。
    【解决方案6】:

    如果您经常需要这些数据,而不是每次拉取数据时都计算它,为什么不添加一列并在添加行时计算/填充它?

    (Remus 的复合索引将使查询更快;只运行一次应该会更快。)

    【讨论】:

      猜你喜欢
      • 2012-01-31
      • 1970-01-01
      • 1970-01-01
      • 2011-05-04
      • 2018-07-25
      • 1970-01-01
      • 2012-04-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多