Optimizar el índice en una tabla de 2,135,044,521 filas

10

Tengo un problema de E / S con una mesa grande.

Estadísticas generales

La tabla tiene las siguientes características principales:

  • entorno: Azure SQL Database (el nivel es P4 Premium (500 DTU))
  • filas: 2,135,044,521
  • 1,275 particiones usadas
  • índice agrupado y particionado

Modelo

Esta es la implementación de la tabla:

CREATE TABLE [data].[DemoUnitData](
    [UnitID] [bigint] NOT NULL,
    [Timestamp] [datetime] NOT NULL,
    [Value1] [decimal](18, 2) NULL,
    [Value2] [decimal](18, 2) NULL,
    [Value3] [decimal](18, 2) NULL,
    CONSTRAINT [PK_DemoUnitData] PRIMARY KEY CLUSTERED 
    (
        [UnitID] ASC,
        [Timestamp] ASC
    )
)
GO

ALTER TABLE [data].[DemoUnitData] WITH NOCHECK ADD CONSTRAINT [FK_DemoUnitData_Unit] FOREIGN KEY([UnitID])
REFERENCES [model].[Unit] ([ID])
GO

ALTER TABLE [data].[DemoUnitData] CHECK CONSTRAINT [FK_DemoUnitData_Unit]
GO

La partición está relacionada con esto:

CREATE PARTITION SCHEME [DailyPartitionSchema] AS PARTITION [DailyPartitionFunction] ALL TO ([PRIMARY])

CREATE PARTITION FUNCTION [DailyPartitionFunction] (datetime) AS RANGE RIGHT
FOR VALUES (N'2017-07-25T00:00:00.000', N'2017-07-26T00:00:00.000', N'2017-07-27T00:00:00.000', ... )

Calidad de servicio

Creo que los índices y las estadísticas se mantienen bien todas las noches mediante la reconstrucción / reorganización / actualización incremental.

Estas son las estadísticas de índice actuales de las particiones de índice más utilizadas:

Estadísticas de partición

Estas son las propiedades estadísticas actuales de las particiones más utilizadas:

Estadísticas

Problema

Ejecuto una consulta simple en una alta frecuencia contra la tabla.

SELECT [UnitID]
    ,[Timestamp]
    ,[Value1]
    ,[Value2]
    ,[Value3]
FROM [data].[DemoUnitData]
WHERE [UnitID] = 8877 AND [Timestamp] >= '2018-03-01' AND [Timestamp] < '2018-03-13'
OPTION (MAXDOP 1)

exce cuenta

El plan de ejecución se ve así: https://www.brentozar.com/pastetheplan/?id=rJvI_4TtG

Mi problema es que estas consultas producen una cantidad extremadamente alta de operaciones de E / S que resultan en un cuello de botella de PAGEIOLATCH_SHesperas.

top espera

Pregunta

He leído que las PAGEIOLATCH_SHesperas a menudo están relacionadas con índices no bien optimizados. ¿Hay alguna recomendación que tenga para mí sobre cómo reducir las operaciones de E / S? ¿Quizás agregando un mejor índice?


Respuesta 1 - relacionada con el comentario de @ S4V1N

El plan de consulta publicado fue de una consulta que ejecuté en SSMS. Después de tu comentario, investigo un poco sobre el historial del servidor. La consulta accidental superada por el servicio se ve un poco diferente (EntityFramework relacionado).

(@p__linq__0 bigint,@p__linq__1 datetime2(7),@p__linq__2 datetime2(7)) 

SELECT 1 AS [C1], [Extent1] 
   .[Timestamp] AS [Timestamp], [Extent1] 
   .[Value1] AS [Value1], [Extent1] 
   .[Value2] AS [Value2], [Extent1] 
   .[Value3] AS [Value3]  
FROM [data].[DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2) OPTION (MAXDOP 1) 

Además, el plan se ve diferente:

https://www.brentozar.com/pastetheplan/?id=H1fhALpKG

o

https://www.brentozar.com/pastetheplan/?id=S1DFQvpKz

Y como puede ver aquí, nuestro rendimiento de la base de datos apenas está influenciado por esta consulta.

SQL superior

Respuesta 2 - relacionada con la respuesta de @Joe Obbish

Para probar la solución, reemplacé Entity Framework con un simple SqlCommand. ¡El resultado fue un increíble aumento de rendimiento!

El plan de consulta ahora es el mismo que en SSMS y las lecturas y escrituras lógicas caen a ~ 8 por ejecución.

¡La carga general de E / S cae a casi 0! Caída de E / S

También explica por qué obtengo una gran caída de rendimiento después de cambiar el rango de partición de mensual a diario. La falta de eliminación de la partición resultó en más particiones para escanear.

Steffen Mangold
fuente
2
Mirando el plan de ejecución, esa consulta no parece tan problemática en absoluto, solo ha escaneado las particiones necesarias con poca cantidad de lecturas y no ha reportado esperas de pageiolatch_sh (sos_sched ...). Lo cual es comprensible porque de todos modos no tuvo lecturas físicas. ¿Son esas esperas acumulativas, o tomadas en cierta cantidad de tiempo? Tal vez el problema es alguna otra consulta después de todo.
S4V1N
Le publiqué una respuesta detallada @ S4V1N arriba
Steffen Mangold

Respuestas:

7

Es posible que pueda reducir las PAGEIOLATCH_SHesperas para esta consulta si puede cambiar los tipos de datos generados por el ORM. La Timestampcolumna en su tabla tiene un tipo de datos de DATETIMEpero los parámetros @p__linq__1y @p__linq__2tienen tipos de datos de DATETIME2(7). Esa diferencia es la razón por la cual el plan de consulta para las consultas ORM es mucho más complicado que el primer plan de consulta que publicó que tenía filtros de búsqueda codificados. También puede obtener una pista de esto en el XML:

<ScalarOperator ScalarString="GetRangeWithMismatchedTypes([@p__linq__1],NULL,(22))">

Tal como está, con la consulta ORM no se puede eliminar ninguna partición. Obtendrá al menos algunas lecturas lógicas para cada partición definida en la función de partición, incluso si solo está buscando un día de datos. Dentro de cada partición, obtiene una búsqueda de índice, por lo que SQL Server no tarda mucho en pasar a la siguiente partición, pero quizás todo ese IO se está acumulando.

Hice una reproducción simple para estar seguro. Hay 11 particiones definidas dentro de la función de partición. Para esta consulta:

DECLARE @p__linq__0 bigint = 2000;
DECLARE @p__linq__1 datetime2(7) = '20180103';
DECLARE @p__linq__2 datetime2(7) = '20180104';

SELECT 1 AS [C1]
, [Extent1].[Timestamp] AS [Timestamp]
, [Extent1].[Value1] AS [Value1]
FROM [DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2)
OPTION (MAXDOP 1) ;

Así es como se ve IO:

Tabla 'DemoUnitData'. Escaneo 11, lecturas lógicas 40

Cuando reparo los tipos de datos:

DECLARE @p__linq__0 bigint = 2000;
DECLARE @p__linq__1 datetime = '20180103';
DECLARE @p__linq__2 datetime = '20180104';

SELECT 1 AS [C1]
, [Extent1].[Timestamp] AS [Timestamp]
, [Extent1].[Value1] AS [Value1]
FROM [DemoUnitData] AS [Extent1]  
WHERE ([Extent1].[UnitID] = @p__linq__0)  
AND ([Extent1].[Timestamp] >= @p__linq__1)  
AND ([Extent1].[Timestamp] < @p__linq__2)
OPTION (MAXDOP 1) ;

IO se reduce como resultado de la eliminación de la partición:

Tabla 'DemoUnitData'. Escaneo recuento 2, lecturas lógicas 8

Joe Obbish
fuente