Optimiser l'index sur une table de lignes de 2 135 044 521

10

J'ai un problème d'E / S avec une grande table.

Statistiques générales

Le tableau présente les principales caractéristiques suivantes:

  • environnement: Azure SQL Database (le niveau est P4 Premium (500 DTU))
  • rangées: 2,135,044,521
  • 1275 partitions utilisées
  • index clusterisé et partitionné

Modèle

Voici l'implémentation de la table:

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

Le partitionnement est lié à ceci:

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', ... )

Qualité de service

Je pense que les index et les statistiques sont bien entretenus chaque nuit par une reconstruction / réorganisation / mise à jour incrémentielle.

Voici les statistiques d'index actuelles des partitions d'index les plus utilisées:

Statistiques de partition

Voici les propriétés statistiques actuelles des partitions les plus utilisées:

Statistiques

Problème

J'exécute une simple requête sur une haute fréquence contre la table.

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)

nombre d'exce

Le plan d'exécution ressemble à ceci: https://www.brentozar.com/pastetheplan/?id=rJvI_4TtG

Mon problème est que ces requêtes produisent une quantité extrêmement élevée d'opérations d'E / S entraînant un goulot d'étranglement des PAGEIOLATCH_SHattentes.

top attend

Question

J'ai lu que les PAGEIOLATCH_SHattentes sont souvent liées à des index mal optimisés. Avez-vous des recommandations à me faire pour réduire les opérations d'E / S? Peut-être en ajoutant un meilleur index?


Réponse 1 - liée au commentaire de @ S4V1N

Le plan de requête publié provenait d'une requête que j'ai exécutée dans SSMS. Après votre commentaire, je fais quelques recherches sur l'historique du serveur. La requête accual extraite du service est un peu différente (liée à EntityFramework).

(@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) 

De plus, le plan est différent:

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

ou

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

Et comme vous pouvez le voir ici, nos performances DB ne sont guère influencées par cette requête.

Top SQL

Réponse 2 - liée à la réponse de @Joe Obbish

Pour tester la solution, j'ai remplacé Entity Framework par un simple SqlCommand. Le résultat a été une incroyable amélioration des performances!

Le plan de requête est maintenant le même que dans SSMS et les lectures et écritures logiques tombent à ~ 8 par exécution.

La charge globale d'E / S chute à presque 0! Suppression d'E / S

Cela explique également pourquoi j'obtiens une baisse importante des performances après avoir changé la plage de partition de mensuel à quotidien. L'absence d'élimination de partition a entraîné davantage de partitions à analyser.

Steffen Mangold
la source
2
En regardant le plan d'exécution, cette requête ne semble pas du tout problématique: elle n'a analysé que les partitions nécessaires avec un faible nombre de lectures et n'a pas signalé à la place les attentes de pageiolatch_sh (sos_sched ..). Ce qui est compréhensible car vous n'aviez de toute façon aucune lecture physique. S'agit-il de temps d'attente cumulés ou d'un certain temps? Peut-être que le problème est une autre requête après tout.
S4V1N
Je vous ai posté une réponse détaillée @ S4V1N ci
Steffen Mangold

Réponses:

7

Vous pourrez peut-être réduire les temps d' PAGEIOLATCH_SHattente pour cette requête si vous pouvez modifier les types de données générés par l'ORM. La Timestampcolonne de votre table a un type de données DATETIMEmais les paramètres @p__linq__1et @p__linq__2des types de données DATETIME2(7). Cette différence explique pourquoi le plan de requête pour les requêtes ORM est tellement plus compliqué que le premier plan de requête que vous avez publié qui avait des filtres de recherche codés en dur. Vous pouvez également obtenir un indice de ceci dans le XML:

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

En l'état, avec la requête ORM, vous ne pouvez obtenir aucune élimination de partition. Vous obtiendrez au moins quelques lectures logiques pour chaque partition définie dans la fonction de partition, même si vous recherchez simplement une journée de données. Dans chaque partition, vous obtenez une recherche d'index, il ne faut donc pas longtemps à SQL Server pour passer à la partition suivante, mais peut-être que toute cette entrée-sortie s'accumule.

J'ai fait une simple reproduction pour être sûr. Il y a 11 partitions définies dans la fonction de partition. Pour cette requête:

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) ;

Voici à quoi ressemble IO:

Tableau 'DemoUnitData'. Nombre de numérisations 11, lectures logiques 40

Lorsque je corrige les types de données:

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 est réduit en raison de l'élimination de la partition:

Tableau 'DemoUnitData'. Nombre de scans 2, lectures logiques 8

Joe Obbish
la source