巨大(100,000,000+)表的GROUP TOP(1)BY GROUP


8

设定

我有一个约115,382,254行的巨大表。该表相对简单,并且记录了应用程序的处理操作。

CREATE TABLE [data].[OperationData](
    [SourceDeciveID] [bigint] NOT NULL,
    [FileSource] [nvarchar](256) NOT NULL,
    [Size] [bigint] NULL,
    [Begin] [datetime2](7) NULL,
    [End] [datetime2](7) NOT NULL,
    [Date]  AS (isnull(CONVERT([date],[End]),CONVERT([date],'19000101',(112)))) PERSISTED NOT NULL,
    [DataSetCount] [bigint] NULL,
    [Result] [int] NULL,
    [Error] [nvarchar](max) NULL,
    [Status] [int] NULL,
 CONSTRAINT [PK_OperationData] PRIMARY KEY CLUSTERED 
(
    [SourceDeviceID] ASC,
    [FileSource] ASC,
    [End] ASC
))

CREATE TABLE [model].[SourceDevice](
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NULL,
 CONSTRAINT [PK_DataLogger] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
))

ALTER TABLE [data].[OperationData]  WITH CHECK ADD  CONSTRAINT [FK_OperationData_SourceDevice] FOREIGN KEY([SourceDeviceID])
REFERENCES [model].[SourceDevice] ([ID])

该表每天约有500个簇。

隔断

在此处输入图片说明

而且,该表已通过PK很好地索引了,统计数据是最新的,并且INDEXer每晚都受到损坏。

基于索引的SELECT很快,我们对此没有任何问题。

问题

我需要知道的最后(TOP)行,[End]并按进行分区[SourceDeciveID]。获得[OperationData]每个源设备的最后一个。

我需要找到一种很好的方法来解决此问题,而又不能使数据库达到极限。


努力1

第一次尝试是显而易见的GROUP BYSELECT OVER PARTITION BY查询。这里的问题也很明显,每个查询都必须扫描非常大的分区顺序/查找第一行。因此查询非常慢,并且对IO的影响很大。

示例查询1

;WITH cte AS
(
   SELECT *,
         ROW_NUMBER() OVER (PARTITION BY [SourceDeciveID] ORDER BY [End] DESC) AS rn
   FROM [data].[OperationData]
)
SELECT *
FROM cte
WHERE rn = 1

查询示例2

SELECT *
FROM [data].[OperationData] AS d 
CROSS APPLY 
(
   SELECT TOP 1 *
   FROM [data].[OperationData] 
   WHERE [SourceDeciveID] = d.[SourceDeciveID]
   ORDER BY [End] DESC
) AS ds

失败!

努力2

我创建了一个帮助表,以始终保存对TOP行的引用。

CREATE TABLE [data].[LastOperationData](
    [SourceDeciveID] [bigint] NOT NULL,
    [FileSource] [nvarchar](256) NOT NULL,
    [End] [datetime2](7) NOT NULL,
 CONSTRAINT [PK_LastOperationData] PRIMARY KEY CLUSTERED 
(
    [SourceDeciveID] ASC
)

ALTER TABLE [data].[LastOperationData]  WITH CHECK ADD  CONSTRAINT [FK_LastOperationData_OperationData] FOREIGN KEY([SourceDeciveID], [FileSource], [End])
REFERENCES [data].[OperationData] ([SourceDeciveID], [FileSource], [End])

为了填充表,创建了一个触发器,如果[End]插入更高的列,则该触发器将始终添加/更新源行。

CREATE TRIGGER [data].[OperationData_Last]
   ON  [data].[OperationData]
   AFTER INSERT
AS 
BEGIN
    SET NOCOUNT ON;

    MERGE [data].[LastOperationData] AS [target]
    USING (SELECT [SourceDeciveID], [FileSource], [End] FROM inserted) AS [source] ([SourceDeciveID], [FileSource], [End])  
    ON ([target].[SourceDeciveID] = [FileSource].[SourceDeciveID])

    WHEN MATCHED AND [target].[End] < [source].[End] THEN
        UPDATE SET [target].[FileSource] = source.[FileSource], [target].[End] = source.[End]

    WHEN NOT MATCHED THEN  
        INSERT ([SourceDeciveID], [FileSource], [End])  
        VALUES (source.[SourceDeciveID], source.[FileSource], source.[End]);

END

这里的问题是,它也具有非常大的IO影响,我不知道为什么。

如您在查询计划中所见,它还对整个[OperationData]表执行扫描。

它对我的数据库有巨大的总体影响。 统计资料

失败!


2
在您的第一个代码块中,我看不到聚簇索引的第一列来自何处-是吗?
George.Palacios

是的,抱歉,SSMS并未将其包括在CREATE TABLE脚本中,但是在查询计划中您将看到分区。我将编辑问题。
Steffen Mangold

不是多余的索引,因为PRIMARY KEY CLUSTERED您认为其中包含的索引可能会有所帮助?
Steffen Mangold

很抱歉,这是一个错误,我更清楚地修改了问题的名称,以进行更正。
Steffen Mangold

@ypercubeᵀᴹ是的,因为SELECT [SourceID], [Source], [End] FROM inserted一些如何在上扫描表[OperationData]
斯特芬·曼戈尔德

Answers:


9

如果您有一个SourceID值表,并且在主表上有一个索引,请(SourceID, End) include (othercolumns)使用OUTER APPLY

SELECT d.*
FROM dbo.Sources s
OUTER APPLY (SELECT TOP (1) *
    FROM data.OperationData d
    WHERE d.SourceID = s.SourceID
    ORDER BY d.[End] DESC) d;

如果您知道只是在使用最新的分区,则可以在End上添加一个过滤器,例如 AND d.[End] > DATEADD(day, -1, GETDATE())

编辑:因为您的聚集索引为on SourceID, Source, End),所以也将Source放入Sources表中,并且也可以将其加入。然后,您不需要新的索引。

SELECT d.*
FROM dbo.Sources s -- Small table
OUTER APPLY (SELECT TOP (1) *
    FROM data.OperationData d -- Big table quick seeks
    WHERE d.SourceID = s.SourceID
    AND d.Source = s.Source
    AND d.[End] > DATEADD(day, -1, GETDATE()) -- If you’re partitioning on [End], do this for partition elimination
    ORDER BY d.[End] DESC) d;

索引确实加快了查询速度。随之而来的第二个问题是,在如此庞大的表上进行未分区索引几乎是无法维护的。在所有“大数据”表上,我们使用分区索引器。它们可以通过分区保持在线分区。索引器分区后,问题就很老了,因为他必须遍历每个分区。
Steffen Mangold

1
@SteffenMangold:越少的数据在索引中更好的(只要有你需要的一切),但不包括物化视图,聚集索引具有最大可能的数据量。存在聚簇索引是因为按键获取所有数据是常态。在这种情况下,您将获取所有数据,但实际上并不是通过密钥来获取,而是通过密钥的一部分来获取。您需要一个可以使用部分键查询的索引。
jmoreno

真的很抱歉,但是有一个Source表引用了该sourceID列。列源只是一个文件名。命名有点混乱。对于每个Source设备(sourceID),source在一个时间戳上,一个文件(列)可能只有一个条目。我End也不能做分区消除,因为最新的碎片分散了。这就是为什么我提出触发器解决方案的原因。我认为实时查询在这里不起作用。
Steffen Mangold '18

@Rob Farley我编辑的问题更清楚了
Steffen Mangold

使用分区,您会发现它可以对每个分区进行所有查找。使用额外的谓词,您可以做到这一点,以使它不会困扰所有的人,而只会影响其中的一部分。如果需要的话,一个月。
罗布·法利
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.