针对每个N的最新记录的最佳执行查询

时间:2011-09-22 13:17:05

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

以下是我发现自己的情景。

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

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上一直在查看这个问题的回答,我已经做了很多谷歌搜索,似乎有3或4种常见的方法在SQL Server 2005及更高版本上执行此操作。

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

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

使用表中的当前数据量,执行大约需要6秒,这在合理的限制范围内,但是在实时环境中,表将包含的数据量开始执行得太慢。

查看执行计划,我关注的是SQL Server正在做什么来返回行。

我无法发布执行计划图像,因为我的声誉不够高,但索引扫描正在解析表格中的每一行,这使得查询速度下降太多。

Execution Plan

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

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

我对表结构有一些灵活性(虽然程度有限)所以我可以在数据库中添加索引,索引视图等甚至是其他表。

我非常感谢这里的任何帮助。

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

3 个答案:

答案 0 :(得分:7)

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

有关3种方法的性能比较,请参阅Optimizing TOP N Per Group Queries

如果你的情况下只有少量车辆有数百万行,我会在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  

答案 1 :(得分:0)

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

作为旁注,这回归了多少记录?如果你收到数十万行,你的延迟可能是网络开销。

答案 2 :(得分:0)

试试这个:

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

预计会进行表或索引扫描,因为您没有以任何方式过滤数据。你要求所有VehicleIDs的最新TimeStamp - 查询引擎要查看每一行以找到最新的TimeStamp。

您可以通过缩小返回的列数(不使用SELECT *),并提供包含VehicleID + TimeStamp的索引来帮助解决。