J'ai une énorme table d'environ 115 382 254 lignes. Le tableau est relativement simple et enregistre les opérations du processus d'application.
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])
Le tableau est regroupé en environ 500 grappes et sur une base quotidienne.
De plus, le tableau est bien indexé par PK, les statistiques sont à jour et l'INDEXer est défragmenté chaque nuit.
Les SELECT basés sur un index sont rapides comme l'éclair et nous n'avons eu aucun problème avec cela.
J'ai besoin de connaître la dernière ligne (TOP) par [End]
et partitionné par [SourceDeciveID]
. Pour obtenir le dernier [OperationData]
de chaque périphérique source.
Je dois trouver un moyen de résoudre ce problème dans le bon sens et sans amener la DB aux limites.
Le premier essai était évident GROUP BY
ou SELECT OVER PARTITION BY
requete. Le problème ici est également évident, chaque requête doit parcourir l'ordre des partitions/trouver la ligne du haut. La requête est donc très lente et a un impact IO très élevé.
;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
SELECT *
FROM [data].[OperationData] AS d
CROSS APPLY
(
SELECT TOP 1 *
FROM [data].[OperationData]
WHERE [SourceDeciveID] = d.[SourceDeciveID]
ORDER BY [End] DESC
) AS ds
ÉCHOUÉ!
J'ai créé une table d'aide pour toujours contenir une référence à la ligne 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])
Pour remplir le tableau, vous avez créé un déclencheur pour toujours ajouter/mettre à jour la ligne source si elle est supérieure [End]
la colonne est insérée.
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
Le problème ici est qu'il a également un très grand impact IO et je ne sais pas pourquoi.
Comme vous pouvez le voir ici dans le plan de requête il exécute également une analyse sur l'ensemble [OperationData]
table.
Cela a un impact global énorme sur ma base de données.
ÉCHOUÉ!
Si vous avez une table de valeurs SourceID
et un index sur votre table principale sur (SourceID, End) include (othercolumns)
, Utilisez simplement 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;
Si vous savez que vous n’êtes qu’après votre dernière partition, vous pouvez inclure un filtre à la fin, comme AND d.[End] > DATEADD(day, -1, GETDATE())
Edit: Parce que votre index cluster est sur SourceID, Source, End)
, Placez également Source dans votre table Sources et joignez-vous à cela également. Ensuite, vous n'avez pas besoin du nouvel index.
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;