Aquí está el resumen: estoy haciendo una consulta de selección. Cada columna de las cláusulas WHERE
y se ORDER BY
encuentra en un único índice no agrupado IX_MachineryId_DateRecorded
, como parte de la clave o como INCLUDE
columnas. Estoy seleccionando todas las columnas, de modo que resulte en una búsqueda de marcadores, pero solo estoy tomando TOP (1)
, por lo que seguramente el servidor puede decir que la búsqueda solo debe hacerse una vez, al final.
Lo más importante, cuando fuerzo la consulta a usar el índice IX_MachineryId_DateRecorded
, se ejecuta en menos de un segundo. Si dejo que el servidor decida qué índice usar, elige IX_MachineryId
, y toma hasta un minuto. Eso realmente me sugiere que hice bien el índice y que el servidor simplemente está tomando una mala decisión. ¿Por qué?
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]);
La tabla está dividida en rangos de mes (aunque todavía no entiendo lo que está pasando allí).
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 consulta que normalmente ejecutaría:
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 consulta: https://www.brentozar.com/pastetheplan/?id=r1c-RpxNx
Plan de consulta con índice forzado: https://www.brentozar.com/pastetheplan/?id=SywwTagVe
Los planes incluidos son los planes de ejecución reales, pero en la base de datos provisional (aproximadamente 1/100 del tamaño de la vida). Dudo en jugar con la base de datos en vivo porque solo comencé en esta empresa hace aproximadamente un mes.
Tengo la sensación de que se debe a la partición, y mi consulta generalmente abarca cada partición (por ejemplo, cuando quiero obtener la primera o la última OperationalSeconds
vez registrada para una máquina). Sin embargo, las consultas que he estado escribiendo a mano se ejecutan entre 10 y 100 veces más rápido de lo que EntityFramework ha generado, por lo que solo voy a hacer un procedimiento almacenado.