Installer
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.
Problème
J'ai besoin de connaître la dernière ligne (TOP) par [End]
et partitionnée par [SourceDeciveID]
. Pour obtenir le dernier [OperationData]
de chaque périphérique source.
Question
Je dois trouver un moyen de résoudre ce problème dans le bon sens et sans amener la DB aux limites.
Effort 1
Le premier essai était évident GROUP BY
ou une SELECT OVER PARTITION BY
requête. 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 d'E / S très élevé.
Exemple de requête 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
Exemple de requête 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
ÉCHOUÉ!
Effort 2
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 une [End]
colonne supérieure 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 que cela a également un impact très important sur les E / S 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 toute la [OperationData]
table.
Cela a un impact global énorme sur ma base de données.
ÉCHOUÉ!
la source
CREATE TABLE
script mais à l'intérieur du plan de requête, vous verrez les partitions. Je vais modifier la question.PRIMARY KEY CLUSTERED
vous pensez que cela peut aider?SELECT [SourceID], [Source], [End] FROM inserted
certains comment faire un scan de table sur le[OperationData]
.Réponses:
Si vous avez une table de
SourceID
valeurs et un index sur votre table principale(SourceID, End) include (othercolumns)
, utilisez simplementOUTER APPLY
.Si vous savez que vous ne recherchez que votre dernière partition, vous pouvez inclure un filtre à la fin, comme
AND d.[End] > DATEADD(day, -1, GETDATE())
Modifier: Parce que votre index cluster est activé
SourceID, Source, End)
, placez également Source dans votre table Sources et joignez-vous à cela également. Ensuite, vous n'avez pas besoin du nouvel index.la source
Source
tableau référençant lasourceID
colonne. La source de la colonne n'est qu'un nom de fichier. C'est un nom un peu déroutant. Pour chaqueSource
périphérique (sourceID), il ne peut y avoir qu'une seule entrée pour un fichiersource
(colonne) à un horodatage. De plus, je ne peux pas éliminer les partitions car la plus récenteEnd
est largement fragmentée. C'est pourquoi j'ai trouvé la solution de déclenchement. Je pense qu'une requête en direct ne fonctionnera pas ici.