Voici le bilan: je fais une requête de sélection. Chaque colonne des clauses WHERE
et se ORDER BY
trouve dans un index non cluster unique IX_MachineryId_DateRecorded
, soit en tant que partie de la clé, soit en tant que INCLUDE
colonnes. Je sélectionne toutes les colonnes, ce qui entraînera une recherche de signet, mais je ne prends TOP (1)
, donc le serveur peut sûrement dire que la recherche ne doit être effectuée qu'une seule fois, à la fin.
Plus important encore, lorsque je force la requête à utiliser l'index IX_MachineryId_DateRecorded
, elle s'exécute en moins d'une seconde. Si je laisse le serveur décider quel index utiliser, il choisit IX_MachineryId
et cela prend jusqu'à une minute. Cela me suggère vraiment que j'ai bien fait l'index, et le serveur prend juste une mauvaise décision. Pourquoi?
CREATE TABLE [dbo].[MachineryReading] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Location] [sys].[geometry] NULL,
[Latitude] FLOAT (53) NOT NULL,
[Longitude] FLOAT (53) NOT NULL,
[Altitude] FLOAT (53) NULL,
[Odometer] INT NULL,
[Speed] FLOAT (53) NULL,
[BatteryLevel] INT NULL,
[PinFlags] BIGINT NOT NULL,
[DateRecorded] DATETIME NOT NULL,
[DateReceived] DATETIME NOT NULL,
[Satellites] INT NOT NULL,
[HDOP] FLOAT (53) NOT NULL,
[MachineryId] INT NOT NULL,
[TrackerId] INT NOT NULL,
[ReportType] NVARCHAR (1) NULL,
[FixStatus] INT DEFAULT ((0)) NOT NULL,
[AlarmStatus] INT DEFAULT ((0)) NOT NULL,
[OperationalSeconds] INT DEFAULT ((0)) NOT NULL,
CONSTRAINT [PK_dbo.MachineryReading] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_dbo.MachineryReading_dbo.Machinery_MachineryId] FOREIGN KEY ([MachineryId]) REFERENCES [dbo].[Machinery] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_dbo.MachineryReading_dbo.Tracker_TrackerId] FOREIGN KEY ([TrackerId]) REFERENCES [dbo].[Tracker] ([Id]) ON DELETE CASCADE
);
GO
CREATE NONCLUSTERED INDEX [IX_MachineryId]
ON [dbo].[MachineryReading]([MachineryId] ASC);
GO
CREATE NONCLUSTERED INDEX [IX_TrackerId]
ON [dbo].[MachineryReading]([TrackerId] ASC);
GO
CREATE NONCLUSTERED INDEX [IX_MachineryId_DateRecorded]
ON [dbo].[MachineryReading]([MachineryId] ASC, [DateRecorded] ASC)
INCLUDE([OperationalSeconds], [FixStatus]);
Le tableau est divisé en plages de mois (bien que je ne comprenne toujours pas vraiment ce qui se passe là-bas).
ALTER PARTITION SCHEME PartitionSchemeMonthRange NEXT USED [Primary]
ALTER PARTITION FUNCTION [PartitionFunctionMonthRange]() SPLIT RANGE(N'2016-01-01T00:00:00.000')
ALTER PARTITION SCHEME PartitionSchemeMonthRange NEXT USED [Primary]
ALTER PARTITION FUNCTION [PartitionFunctionMonthRange]() SPLIT RANGE(N'2016-02-01T00:00:00.000')
...
CREATE UNIQUE CLUSTERED INDEX [PK_dbo.MachineryReadingPs] ON MachineryReading(DateRecorded, Id) ON PartitionSchemeMonthRange(DateRecorded)
La requête que j'exécuterais normalement:
SELECT TOP (1) [Id], [Location], [Latitude], [Longitude], [Altitude], [Odometer], [ReportType], [FixStatus], [AlarmStatus], [Speed], [BatteryLevel], [PinFlags], [DateRecorded], [DateReceived], [Satellites], [HDOP], [OperationalSeconds], [MachineryId], [TrackerId]
FROM [dbo].[MachineryReading]
--WITH(INDEX(IX_MachineryId_DateRecorded)) --This makes all the difference
WHERE ([MachineryId] = @p__linq__0) AND ([DateRecorded] >= @p__linq__1) AND ([DateRecorded] < @p__linq__2) AND ([OperationalSeconds] > 0)
ORDER BY [DateRecorded] ASC
Plan de requête: https://www.brentozar.com/pastetheplan/?id=r1c-RpxNx
Plan de requête avec index forcé: https://www.brentozar.com/pastetheplan/?id=SywwTagVe
Les plans inclus sont les plans d'exécution réels, mais sur la base de données de staging (environ 1 / 100e de la taille du live). J'hésite à jouer avec la base de données en direct car je n'ai commencé dans cette entreprise qu'il y a environ un mois.
J'ai l'impression que c'est à cause du partitionnement, et ma requête couvre généralement chaque partition (par exemple lorsque je veux obtenir la première ou la dernière OperationalSeconds
jamais enregistrée pour une machine). Cependant, les requêtes que j'ai écrites à la main fonctionnent toutes 10 à 100 fois plus rapidement que ce que EntityFramework a généré, donc je vais simplement créer une procédure stockée.