【问题标题】:Optimal performing query for latest record for each N每个 N 的最新记录的最佳执行查询
【发布时间】:2011-11-22 20:11:53
【问题描述】:

这是我发现自己所处的场景。

我有一个相当大的表,我需要从中查询最新记录。以下是查询基本列的创建:

CREATE TABLE [dbo].[ChannelValue](
   [ID] [bigint] IDENTITY(1,1) NOT NULL,
   [UpdateRecord] [bit] NOT NULL,
   [VehicleID] [int] NOT NULL,
   [UnitID] [int] NOT NULL,
   [RecordInsert] [datetime] NOT NULL,
   [TimeStamp] [datetime] NOT NULL
   ) ON [PRIMARY]
GO

ID 列是主键,VehicleID 和 TimeStamp 上存在非聚集索引

CREATE NONCLUSTERED INDEX [IX_ChannelValue_TimeStamp_VehicleID] ON [dbo].[ChannelValue] 
(
    [TimeStamp] ASC,
    [VehicleID] ASC
)ON [PRIMARY]
GO

我正在优化我的查询的表有超过 2300 万行,只是查询需要操作的大小的十分之一。

我需要返回每个 VehicleID 的最新行。

我一直在 StackOverflow 上查看对这个问题的回答,并且在 Google 上进行了相当多的搜索,在 SQL Server 2005 及更高版本上似乎有 3 或 4 种常用方法。

到目前为止,我发现最快的方法是以下查询:

SELECT cv.*
FROM ChannelValue cv
WHERE cv.TimeStamp = (
SELECT
    MAX(TimeStamp)
FROM ChannelValue
WHERE ChannelValue.VehicleID = cv.VehicleID
)

根据表中的当前数据量,执行大约需要 6 秒,这在合理的范围内,但是随着表将在实时环境中包含的数据量,查询开始执行太慢。

查看执行计划,我关心的是 SQL Server 为返回行所做的工作。

我无法发布执行计划图像,因为我的 Reputation 不够高,但索引扫描正在解析表中的每一行,这大大降低了查询速度。

我尝试使用几种不同的方法重写查询,包括使用 SQL 2005 分区方法,如下所示:

WITH cte
AS (
    SELECT *,
    ROW_NUMBER() OVER(PARTITION BY VehicleID ORDER BY TimeStamp DESC) AS seq
     FROM ChannelValue
)

SELECT
   VehicleID,
   TimeStamp,
   Col1
FROM cte
WHERE seq = 1

但该查询的性能甚至差很多。

我尝试过像这样重构查询,但结果速度和查询执行计划几乎相同:

SELECT cv.*
FROM (
   SELECT VehicleID
    ,MAX(TimeStamp) AS [TimeStamp]
   FROM ChannelValue
   GROUP BY VehicleID
) AS [q]
INNER JOIN ChannelValue cv
   ON cv.VehicleID = q.VehicleID
   AND cv.TimeStamp = q.TimeStamp

我在表结构方面有一些灵活性(尽管程度有限),因此我可以向数据库中添加索引、索引视图等,甚至是其他表。

如果能提供任何帮助,我将不胜感激。

编辑添加了执行计划图片的链接。

【问题讨论】:

  • 问题已投票,您现在应该可以发布您的图片了 :)
  • 您能否在 VehicleID 和 TimeStamp 上显示非聚集索引的 create index 命令?
  • 您是否尝试过在 TimeStamp 上添加索引?另请注意,测试时间戳相等性可能会导致问题。
  • 我添加了非聚集索引创建脚本。我也玩过索引,但查询执行计划一直遇到同样的问题。
  • 如果您总是想要最新的日期,那么也许可以尝试使用(VehicleID, Timestamp DESC) 上的索引,但我认为您无法避免扫描(但至少在这种情况下会订购)。

标签: sql-server performance tsql greatest-n-per-group database-performance


【解决方案1】:

取决于您的数据(每组有多少行?)和您的索引。

请参阅Optimizing TOP N Per Group Queries 了解 3 种方法的一些性能比较。

在您的情况下,只有少数车辆有数百万行,我会在 VehicleID, Timestamp 上添加一个索引并执行

SELECT CA.*
FROM   Vehicles V
       CROSS APPLY (SELECT TOP 1 *
                    FROM   ChannelValue CV
                    WHERE  CV.VehicleID = V.VehicleID
                    ORDER  BY TimeStamp DESC) CA  

【讨论】:

  • 仅链接的答案应该是真正的评论(所以“管理层”有时会告诉我......)
  • @gbn - 我很高兴这应该回答 OP 的问题!
  • 谢谢马丁,我现在正忙着看那篇文章。
  • @AJax - 在 2300 万行表中有多少不同的 VehicleID 数字?
  • 在我的测试环境中有 8 个不同的 VehicleID,但在实际环境中大约有 286 个。
【解决方案2】:

如果您的记录是按顺序插入的,则将查询中的 TimeStamp 替换为 ID 可能会有所不同。

顺便说一句,这返回了多少条记录?如果您要返回数十万行,您的延迟可能是网络开销。

【讨论】:

  • 不幸的是,记录并不总是按顺序插入,因此 VehicleID 和 TimeStamp 的组合将是确定每辆车的最新记录的唯一方法。
  • 查询将只为每个 Vehicle 返回 1 条记录,因此无论表中存在多少百万行,最多将返回 286 条记录。
【解决方案3】:

试试这个:

SELECT SequencedChannelValue.* -- Specify only the columns you need, exclude the SequencedChannelValue
FROM
    (
        SELECT 
            ChannelValue.*,   -- Specify only the columns you need
            SeqValue = ROW_NUMBER() OVER(PARTITION BY VehicleID ORDER BY TimeStamp DESC)
        FROM ChannelValue
    ) AS SequencedChannelValue
WHERE SequencedChannelValue.SeqValue = 1

需要进行表或索引扫描,因为您没有以任何方式过滤数据。您要求所有 VehicleID 的最新时间戳 - 查询引擎必须查看每一行以找到最新的时间戳。

您可以通过缩小返回的列数(不要使用 SELECT *)以及提供由 VehicleID + TimeStamp 组成的索引来帮助解决此问题。

【讨论】:

  • 我有一个关于 VehicleID 和 TimeStamp 的索引,并且我尝试了 Partition By 方法,但它的表现要差得多。为了安全起见,我已经运行了您发布的查询,并且需要几分钟才能返回数据。
  • 请注意我提到限制返回的列。如果您要从表中返回每一列,那么仅使用 VehicleID 和 TimeStamp 的索引是没有用的。我的语句的查询计划是什么样的?
  • 嗨,Tracy,很遗憾,我需要表格中的所有列。这是查询执行计划图像的链接。它与我尝试使用 ROW_NUMBER() OVER () 方法时得到的执行计划相同。 !Exec Plan
  • 好的,在这种情况下,您的原件可能是您能得到的最好的。要消除聚集索引扫描,您可以使用查询提示来强制使用您的其他索引,但这将被昂贵的书签查找抵消以获取其他列。
猜你喜欢
  • 1970-01-01
  • 2020-07-29
  • 2021-01-10
  • 1970-01-01
  • 2016-11-26
  • 2018-12-12
  • 1970-01-01
  • 2020-06-21
相关资源
最近更新 更多