J'ai un problème énorme avec des pics de processeur à 100% en raison d'un mauvais plan d'exécution utilisé par une requête spécifique. Je passe des semaines à résoudre par moi-même.
Ma base de données
Mon exemple de base de données contient 3 tableaux simplifiés.
[Enregistreur de données]
CREATE TABLE [model].[DataLogger](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[ProjectID] [bigint] NULL,
CONSTRAINT [PK_DataLogger] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
[Onduleur]
CREATE TABLE [model].[Inverter](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[SerialNumber] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Inverter] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY],
CONSTRAINT [UK_Inverter] UNIQUE NONCLUSTERED
(
[DataLoggerID] ASC,
[SerialNumber] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [model].[Inverter] WITH CHECK
ADD CONSTRAINT [FK_Inverter_DataLogger]
FOREIGN KEY([DataLoggerID])
REFERENCES [model].[DataLogger] ([ID])
[InverterData]
CREATE TABLE [data].[InverterData](
[InverterID] [bigint] NOT NULL,
[Timestamp] [datetime] NOT NULL,
[DayYield] [decimal](18, 2) NULL,
CONSTRAINT [PK_InverterData] PRIMARY KEY CLUSTERED
(
[InverterID] ASC,
[Timestamp] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF)
)
Statistiques et maintenance
Le [InverterData]
tableau contient plusieurs millions de lignes (diffère dans plusieurs instances PaaS) partitionnées en jonques mensuelles.
Tous les indexeurs sont défragmentés et toutes les statistiques reconstruites / réorganisées selon les besoins sur un tour quotidien / hebdomadaire.
Ma requête
La requête est générée par Entity Framework et également simple. Mais je cours 1 000 fois par minute et les performances sont essentielles.
SELECT
[Extent1].[InverterID] AS [InverterID],
[Extent1].[DayYield] AS [DayYield]
FROM [data].[InverterDayData] AS [Extent1]
INNER JOIN [model].[Inverter] AS [Extent2] ON [Extent1].[InverterID] = [Extent2].[ID]
INNER JOIN [model].[DataLogger] AS [Extent3] ON [Extent2].[DataLoggerID] = [Extent3].[ID]
WHERE ([Extent3].[ProjectID] = @p__linq__0)
AND ([Extent1].[Date] = @p__linq__1) OPTION (MAXDOP 1)
L' MAXDOP 1
astuce est pour un autre problème avec un plan parallèle parallèle.
Le "bon" plan
Au cours des 90% du temps, le plan utilisé est rapide comme l'éclair et ressemble à ceci:
Le problème
Au cours de la journée, le bon plan est devenu au hasard un plan mauvais et lent.
Le "mauvais" plan est utilisé pendant 10 à 60 minutes, puis changé pour le "bon" plan. Le "mauvais" plan pique le CPU jusqu'à 100% permanent.
Voici à quoi cela ressemble:
Ce que j'essaie jusqu'à présent
Ma première pensée a été Hash Match
le mauvais garçon. J'ai donc modifié la requête avec un nouvel indice.
...Extent1].[Date] = @p__linq__1) OPTION (MAXDOP 1, LOOP JOIN)
Le LOOP JOIN
devrait forcer à utiliser l' Nested Loop
instant de Hash Match
.
Le résultat est que le plan à 90% ressemble à avant. Mais le plan est également devenu aléatoire.
Le "mauvais" plan ressemble maintenant à ceci (l'ordre des boucles de table a changé):
Le CPU jette également un œil à 100% lors du "nouveau mauvais" plan.
Solution?
Il me vient à l'esprit de forcer le «bon» plan. Mais je ne sais pas si c'est une bonne idée.
À l'intérieur du plan se trouve un index recommandé qui inclut toutes les colonnes. Mais cela doublera le tableau complet et ralentira les inserts qui sont très fréquents.
Aidez-moi, s'il vous plaît!
Mise à jour 1 - liée au commentaire @James
Voici les deux plans (certains champs supplémentaires affichés dans le plan, car ils proviennent de la table réelle):
Mauvais plan 2 (boucle imbriquée)
Mise à jour 2 - liée à @David Fowler répondeur
Le mauvais plan intervient sur une valeur de paramètre aléatoire. Donc normalement je @p__linq__1 ='2016-11-26 00:00:00.0000000' @p__linq__0 =20825
le jour du trou et que le mauvais plan arrive sur la même valeur.
Je connais le problème de reniflement des paramètres des procédures stockées et comment les éviter dans SP. Avez-vous une astuce pour moi comment éviter ce problème pour ma requête?
La création de l'index recommandé inclura toutes les colonnes. Cela doublera le tableau complet et ralentira les inserts, qui sont très fréquents. Cela ne «semble» pas juste de construire un index qui clone simplement la table. Je veux aussi doubler la taille des données de cette grande table.
Mise à jour 3 - liée au commentaire de @David Fowler
Cela n'a pas fonctionné non plus et je pense que non. Pour une meilleure compréhension, je vais vous expliquer comment la requête est appelée.
Supposons que j'ai 3 entités dans le [DataLogger]
tableau. Au cours de la journée, j'appelle encore et encore les 3 mêmes requêtes dans un aller-retour:
Requête de base:
...WHERE ([Extent3].[ProjectID] = @p__linq__0) AND ([Extent1].[Date] = @p__linq__1)
Paramètre:
@p__linq__0 = 1; @p__linq__1 = '2018-01-05 00:00:00.0000000'
@p__linq__0 = 2; @p__linq__1 = '2018-01-05 00:00:00.0000000'
@p__linq__0 = 3; @p__linq__1 = '2018-01-05 00:00:00.0000000'
Le paramètre @p__linq__1
est toujours la même date. Mais il choisit le mauvais plan au hasard sur une requête qui s'exécute toutes les fois avec un bon plan avant. Avec le même paramètre!
Mise à jour 4 - liée au commentaire @Nic
L'entretien fonctionne tous les soirs et ressemble à ceci.
Indice
Si un indice est fragmenté à plus de 5%, il est réorganisé ...
ALTER INDEX [{index}] ON [{table}] REORGANIZE
Si un indice est fragmenté à plus de 30%, il est reconstruit ...
ALTER INDEX [{index}] ON [{table}] REBUILD WITH (ONLINE=ON, MAXDOP=1)
Si l'index est partitionné, il sera protégé contre la fragmentation et modifié par partition ...
ALTER INDEX [{index}] ON [{table}] REBUILD PARTITION = {partitionNr} WITH (ONLINE=ON, MAXDOP=1)
Statistiques
Toutes les statistiques seront mises à jour si elles modification_counter
sont supérieures à 0 ...
UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH FULLSCAN
ou sur partitionné ..
UPDATE STATISTICS [{schema}].[{object}] ([{stats}]) WITH RESAMPLE ON PARTITIONS({partitionNr})
La maintenance inclut toutes les statistiques, également celle générée automatiquement.
la source
Réponses:
Regardez les plans, il y a quelques différences entre le bon et le mauvais. La première chose à noter est que le bon plan effectue une recherche sur InverterDayData alors que les deux mauvais plans effectuent un scan. Pourquoi cela, si vous vérifiez les lignes estimées, vous verrez que le bon plan attend 1 ligne alors que les mauvais plans attendent 6661 et environ 7000 lignes.
Jetez maintenant un œil aux valeurs des paramètres compilés,
Bon plan @ p__linq__1 = '2016-11-26 00: 00: 00.0000000' @ p__linq__0 = 20825
Mauvais plans @ p__linq__1 = '2018-01-03 00: 00: 00.0000000' @ p__linq__0 = 20686
il me semble donc que c'est un problème de reniflage de paramètre, quelles valeurs de paramètre transmettez-vous à cette requête lorsqu'elle fonctionne mal?
Il y a une recommandation d'index dans les mauvais plans sur InverterDayData qui semble raisonnable, j'essaierais de l'exécuter et de voir si cela vous aide. Il peut permettre à SQL d'effectuer une analyse sur la table.
la source
...OPTION (OPTIMIZE FOR UNKNOWN)
indice.