聚合 GROUP BY 瓶颈

Ste*_*old 3 index sql-server-2008 sql-server execution-plan

我有一个SELECT GROUP BY操作的性能瓶颈。

架构

我有一张这样的表:

CREATE TABLE [InverterData](
    [InverterID] [bigint] NOT NULL,
    [TimeStamp] [datetime] NOT NULL,    
    [ValueA] [decimal](18, 2) NULL,
    [ValueB] [decimal](18, 2) NULL
    CONSTRAINT [PrimaryKey_e149e28f-5754-4229-be01-65fafeebce16] PRIMARY KEY CLUSTERED 
    (
        [TimeStamp] DESC,
        [InverterID] ASC
    ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF
    , IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON)
)
Run Code Online (Sandbox Code Playgroud)

Index这样的:

CREATE NONCLUSTERED INDEX [TimeStamp_Power-NonClusteredIndex] ON [dbo].[InverterData]
(
    [InverterID] ASC,
    [TimeStamp] ASC
)
INCLUDE 
(   
    [ValueA],
    [ValueB]
)
Run Code Online (Sandbox Code Playgroud)

[InverterData]表具有以下存储统计信息:

  • 数据空间:26,901.86 MB
  • 行数:131,827,749
  • 分区:真实
  • 分区数:62

用法

使用我描述的模式(加上一些对我的问题不重要的额外表),我可以运行超快速查询,如下所示:

SELECT [TimeStamp], [ValueA], [ValueB]

FROM [InverterData]

JOIN [Inverter] ON [Inverter].[ID] = [InverterData].[InverterID]
JOIN [DataLogger] ON [DataLogger].[ID] = [Inverter].[DataLoggerID]

WHERE [DataLogger].[ProjectID] = 20686
 AND [InverterData].[TimeStamp] >= '20160108'   
 AND [InverterData].[TimeStamp] < '20160109'
Run Code Online (Sandbox Code Playgroud)

执行时间跨度: 178ms

执行计划:

在此处输入图片说明

问题

我现在想SELECT GROUP BY通过 [InverterID] 和 [TimeStamp] 的 15 分钟间隔制作一个。

有这样的想法:

SELECT [InverterID]
 , DATEADD(MINUTE, DATEDIFF(MINUTE, 0, [TimeStamp] ) / 15 * 15, 0) AS [TimeStamp]
 , SUM([ValueA]), SUM([ValueB])

FROM [InverterData]

JOIN [Inverter] ON [Inverter].[ID] = [InverterData].[InverterID]
JOIN [DataLogger] ON [DataLogger].[ID] = [Inverter].[DataLoggerID]

WHERE [DataLogger].[ProjectID] = 20686
 AND [InverterData].[TimeStamp] >= '20160107'
 AND [InverterData].[TimeStamp] < '20160108'

GROUP BY
 [InverterID], DATEADD(MINUTE, DATEDIFF(MINUTE, 0, [InverterData].[TimeStamp] ) / 15 * 15, 0)
Run Code Online (Sandbox Code Playgroud)

执行时间跨度: 4637ms

执行计划:

在此处输入图片说明

尝试

我认为这可能与Sort此处的必要操作有关:

在此处输入图片说明

据我所知,可以SORT通过创建匹配的索引器来避免这种情况。但我不知道如何用我的 15 分钟间隔分组来做到这一点。

正如您所看到的,执行时间跨度SELECT GROUP BY要长得多。但我不知道在哪里以及如何避免瓶颈!?

更新 1(与@Max Vernon 答案相关)

如果可能的话,我想有一个更快的解决方案,我可以灵活地更改间隔(例如 10、15 或 6o 分钟)。所以没有计算列。

Han*_*non 7

您可以向表中添加计算列并根据计算构建索引。

例如,该表将是:

CREATE TABLE dbo.InverterData
(
     InverterID bigint NOT NULL
    , TS datetime NOT NULL    
    , ValueA decimal(18, 2) NULL
    , ValueB decimal(18, 2) NULL
    , TS15 AS (DATEADD(MINUTE, DATEDIFF(MINUTE, 0, TS ) / 15 * 15, 0))
    , CONSTRAINT PK_InverterData PRIMARY KEY CLUSTERED 
    (
        TS DESC,
        InverterID ASC
    ) WITH (
        PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF
        , IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON
        , ALLOW_PAGE_LOCKS = ON
        )
);
Run Code Online (Sandbox Code Playgroud)

我已将TimeStamp列的名称修改为TS因为TimeStamp是保留字。

看了保罗的评论后;我认为一个好的索引可能是:

CREATE INDEX IX_InverterData_ID_TS151 
ON dbo.InverterData (TS15, InverterID);
Run Code Online (Sandbox Code Playgroud)

如果您将WHERE子句更改为对索引时间戳列进行操作,则计划中没有排序。我认为对where条款的更改可能不是问题,因为您似乎整天都在选择。

SELECT InverterData.InverterID
    , InverterData.TS15
FROM InverterData
WHERE InverterData.TS15 >= '20140107'
    AND InverterData.TS15 < '20160108'
GROUP BY InverterData.InverterID
    , InverterData.TS15
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)

当然,我没有在示例中包含其他表,因为您没有提供这些详细信息。

上面查询的计划是,我的示例表中有 100,000 行:

在此处输入图片说明

说了这么多,如果没有实际的表定义,包括分区方案等,很难说这对你来说实际效果如何。

由于添加(非持久化)计算列只是元数据操作,因此修改表应该几乎是即时的。当您知道没有其他事务(或尽可能少)发生时,您仍然可能想要这样做,因此所需的架构锁(虽然非常短暂)不会阻塞。修改表的DDL是:

ALTER TABLE dbo.InverterData
ADD TS15 AS (DATEADD(MINUTE, DATEDIFF(MINUTE, 0, TS ) / 15 * 15, 0));
Run Code Online (Sandbox Code Playgroud)