Requêtes et mises à jour extrêmement lentes après IndexOptimize

12

Base de données SQL Server 2017 Enterprise CU16 14.0.3076.1

Nous avons récemment essayé de passer des tâches de maintenance par défaut de reconstruction d'index à Ola Hallengren IndexOptimize. Les travaux de reconstruction d'index par défaut étaient en cours d'exécution depuis quelques mois sans aucun problème, et les requêtes et les mises à jour fonctionnaient avec des délais d'exécution acceptables. Après avoir exécuté IndexOptimizesur la base de données:

EXECUTE dbo.IndexOptimize
@Databases = 'USER_DATABASES',
@FragmentationLow = NULL,
@FragmentationMedium = 'INDEX_REORGANIZE,INDEX_REBUILD_ONLINE,INDEX_REBUILD_OFFLINE',
@FragmentationHigh = 'INDEX_REBUILD_ONLINE,INDEX_REBUILD_OFFLINE',
@FragmentationLevel1 = 5,
@FragmentationLevel2 = 30,
@UpdateStatistics = 'ALL',
@OnlyModifiedStatistics = 'Y'

les performances étaient extrêmement dégradées. Une déclaration de mise à jour qui a IndexOptimizepris 100 ms avant a pris 78 000 ms après (en utilisant un plan identique), et les requêtes exécutaient également plusieurs ordres de grandeur.

Comme il s'agit toujours d'une base de données de test (nous migrons un système de production depuis Oracle), nous sommes revenus à une sauvegarde et désactivés IndexOptimizeet tout est revenu à la normale.

Cependant, nous aimerions comprendre ce qui IndexOptimizefait différemment du "normal" Index Rebuildqui aurait pu causer cette dégradation extrême des performances afin de nous assurer de l'éviter une fois que nous serons en production. Toute suggestion sur ce qu'il faut rechercher serait grandement appréciée.

Plan d'exécution de l'instruction de mise à jour lorsqu'elle est lente. c'est-à-dire
après IndexOptimize
Plan d'exécution réel (à venir dès que possible)

Je n'ai pas pu voir de différence.
Planifiez la même requête lorsqu'elle est rapide
Plan d'exécution réel

Martin Bergström
la source

Réponses:

11

Je suppose que vous avez défini un taux d'échantillonnage différent entre vos deux approches de maintenance. Je pense que les scripts d'Ola utilisent l'échantillonnage par défaut, sauf si vous spécifiez le @StatisticsSampleparamètre , ce qui ne ressemble pas à ce que vous faites actuellement.

À ce stade, il s'agit de spéculations, mais vous pouvez vérifier le taux d'échantillonnage actuellement utilisé dans vos statistiques en exécutant la requête suivante dans votre base de données:

SELECT  OBJECT_SCHEMA_NAME(st.object_id) + '.' + OBJECT_NAME(st.object_id) AS TableName
    ,   col.name AS ColumnName
    ,   st.name AS StatsName
    ,   sp.last_updated
    ,   sp.rows_sampled
    ,   sp.rows
    ,   (1.0*sp.rows_sampled)/(1.0*sp.rows) AS sample_pct
FROM sys.stats st 
    INNER JOIN sys.stats_columns st_col
        ON st.object_id = st_col.object_id
        AND st.stats_id = st_col.stats_id
    INNER JOIN sys.columns col
        ON st_col.object_id = col.object_id
        AND st_col.column_id = col.column_id
    CROSS APPLY sys.dm_db_stats_properties (st.object_id, st.stats_id) sp
ORDER BY 1, 2

Si vous voyez que cela passe par un 1 (par exemple 100%), c'est probablement votre problème. Peut-être essayez-vous à nouveau les scripts d'Ola, y compris le @StatisticsSampleparamètre avec le pourcentage de retour de cette requête et voyez si cela résout votre problème?


Comme preuve supplémentaire à l'appui de cette théorie, le plan d'exécution XML montre des taux d'échantillonnage très différents pour la requête lente (2,18233%):

<StatisticsInfo LastUpdate="2019-09-01T01:07:46.04" ModificationCount="0" 
    SamplingPercent="2.18233" Statistics="[INDX_UPP_4]" Table="[UPPDRAG]" 
    Schema="[SVALA]" Database="[ulek-sva]" />

Contre la requête rapide (100%):

<StatisticsInfo LastUpdate="2019-08-25T23:01:05.52" ModificationCount="555" 
    SamplingPercent="100" Statistics="[INDX_UPP_4]" Table="[UPPDRAG]" 
    Schema="[SVALA]" Database="[ulek-sva]" />
John Eisbrener
la source
@JoshDarnell LOL, c'est la deuxième occurrence où vous avez trouvé des informations de statistiques de support dans le plan de requête que je n'ai pas pu voir. Merci pour l'édition!
John Eisbrener
Haha j'ai oublié que c'était toi, John! Je te promets de ne pas te traquer 😅
Josh Darnell
@JoshDarnell J'apprécie les informations supplémentaires et c'est un autre bon rappel qu'il y a tellement d'informations dans les plans d'exécution que vous ne devriez pas ignorer.
John Eisbrener
Heureux de vous aider! Et oui, il y a aussi des choses qui me manquent tout le temps (j'ai été brûlé par les statistiques, donc j'ai tendance à y aller rapidement pour voir ce qui se passe).
Josh Darnell
Merci pour cette explication, c'était bien le problème. La plupart des statistiques avaient un taux d'échantillonnage par défaut de 2,2%, mais quelques-unes qui ont été créées après la migration d'Oracle avaient un taux d'échantillonnage de 100%. Il semble que la reconstruction d'index par défaut ait conservé 100%, mais lorsque nous avons utilisé IndexOptimize, il a également appliqué la valeur par défaut de 2,2% à ceux-ci. L'application du paramètre @StatisticsSample et l'exécution à nouveau des requêtes ont vérifié que c'était la cause du problème.
Martin Bergström
5

La réponse de John est la bonne solution, ce n'est qu'un ajout sur les parties du plan d'exécution modifiées et un exemple sur la façon de repérer facilement les différences avec l' explorateur de Sentry One Plan

Une déclaration de mise à jour qui a pris 100 ms avant que IndexOptimize prenne 78 000 ms par la suite (en utilisant un plan identique)

Lorsque vous regardez tous les plans de requête lorsque vos performances ont été dégradées, vous pouvez facilement repérer les différences.

Des performances dégradées

entrez la description de l'image ici

Deux comptes de plus de 35 secondes de temps processeur et de temps écoulé

Performance attendue

entrez la description de l'image ici

Beaucoup mieux

La dégradation principale est deux fois sur cette requête de mise à jour:

UPDATE SVALA.INGÅENDEANALYS
                           SET 
                              UPPDRAGAVSLUTAT = @NEW$AVSLUTAT
                        WHERE INGÅENDEANALYS.ID IN 
                           (
                              SELECT IA.ID
                              FROM 
                                 SVALA.INGÅENDEANALYS  AS IA 
                                    JOIN SVALA.INGÅENDEANALYSX  AS IAX 
                                    ON IAX.INGÅENDEANALYS = IA.ID 
                                    JOIN SVALA.ANALYSMATERIAL  AS AM 
                                    ON AM.ID = IA.ANALYSMATERIALID 
                                    JOIN SVALA.ANALYSMATERIALX  AS AMX 
                                    ON AMX.ANALYSMATERIAL = AM.ID 
                                    JOIN SVALA.INSÄNTMATERIAL  AS IM 
                                    ON IM.ID = AM.INSÄNTMATERIALID 
                                    JOIN SVALA.INSÄNTMATERIALX  AS IMX 
                                    ON IMX.INSÄNTMATERIAL = IM.ID
                              WHERE IM.UPPDRAGSID = SVALA.PKGSVALA$STRIPVERSION(@NEW$ID)
                      )

le plan d'exécution de cette requête avec des performances dégradées

Le plan de requête estimé de cette requête de mise à jour a des estimations très élevées lorsque les performances ont été dégradées:

entrez la description de l'image ici

Alors qu'en réalité (le plan d'exécution réel), il doit encore faire du travail, mais pas le montant fou que les estimations montrent.

Le plus grand impact sur les performances est les deux scans et correspondances de hachage ci-dessous:

Analyse réelle des performances dégradées # 1

entrez la description de l'image ici

Analyse réelle sur les performances dégradées # 2

entrez la description de l'image ici


Le plan d'exécution de cette requête avec les performances attendues

Lorsque vous comparez cela aux estimations (ou aux chiffres réels) du plan de requête avec des performances normales attendues, les différences sont faciles à repérer.

entrez la description de l'image ici

De plus, les deux accès précédents aux tables ne se sont même pas produits:

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

Vous ne voyez pas cette élimination sur la jointure de hachage car l'entrée de génération (en haut) est insérée en premier dans la table de hachage. Ensuite, des valeurs nulles sont sondées dans cette table de hachage, retournant des valeurs nulles.

Randi Vertongen
la source
1
Merci pour la description détaillée des plans, cela m'a beaucoup aidé à comprendre pourquoi le problème est survenu. Je vais certainement jeter un œil à Sentry One Plan Explorer, il a l'air très utile!
Martin Bergström
@ MartinBergström Cela fait plaisir à entendre, merci d'avoir fourni les plans de requête et de nous avoir fourni toutes les informations pertinentes que nous avons demandées dans les commentaires :). La meilleure chose à propos de l'explorateur de plans est qu'il est gratuit! Il peut également fonctionner à l'intérieur de ssms (en cliquant avec le bouton droit sur le plan d'exécution et en appuyant sur "Afficher avec l'explorateur de plans sentinone").
Randi Vertongen
1

Sans plus d'informations, nous ne pouvons que prendre des coups de couteau légèrement informés dans l'obscurité, vous devez donc modifier la question pour en fournir un peu plus. Par exemple, les plans de requête pour cette déclaration de mise à jour pour laquelle vous avez donné les délais, avant et après les opérations de maintenance d'index car les plans peuvent différer en raison des statistiques d'index ayant été mises à jour ( https://www.brentozar.com/pastetheplan / est utile pour cela, plutôt que de remplir la question avec ce qui pourrait être un énorme morceau de XML ou de donner une capture d'écran qui n'inclut pas certaines des informations pertinentes contenues dans le texte du plan).

Cependant, deux points très simples:

  1. Le cycle d'optimisation est-il définitivement terminé? Si vos tests sont en concurrence avec l'IO des reconstructions d'index de longue durée, cela affectera les délais.
  2. Avez-vous testé plusieurs fois? Si la mise à jour est basée sur les données d'une requête qui prend en compte un grand nombre de données (plutôt qu'un simple `UPDATE TheTable SET ThisColumn = 'A Static Value'), il se peut que ces données soient normalement en mémoire mais ont été éliminées dans qui les premières exécutions de requêtes liées seront plus lentes que d'habitude en raison de frapper le disque plutôt que de trouver les pages nécessaires déjà dans le pool de mémoire tampon en mémoire.
David Spillett
la source
Merci d'avoir pris le temps de répondre. J'ai mis à jour la question avec des liens pastetheplan. L'optimisation était définitivement terminée, elle a fonctionné pendant environ 1 heure la veille du problème. Nous avons testé plusieurs fois, et cela a en fait affecté deux copies de la base de données fonctionnant de la même manière dans deux environnements de test différents. L'instruction Update était juste l'exemple le plus simple que j'ai trouvé, il y avait de nombreux autres insertions et sélections affectées
Martin Bergström
Par "plusieurs fois", je voulais dire essayer les mises à jour plusieurs fois après une instance des changements d'index, plutôt que d'exécuter le script d'optimisation d'index plusieurs fois indépendamment (bien que cela soit en soi un moyen utile de vérifier que le résultat est reproductible). Si le vidage de la mémoire est (ou fait partie) du problème, les premières mises à jour à partir de sélections amorceront le pool de tampons afin que les dernières soient potentiellement plus rapides en raison d'une réduction significative des entrées-sorties.
David Spillett
Toutes mes excuses si ma réponse n'était pas claire. Oui, nous avons essayé les mises à jour plusieurs fois. Les ralentissements se sont produits sur une base de données utilisée par les testeurs pour tester l'application et les requêtes et mises à jour ont été exécutées plusieurs fois dans la journée sans amélioration des performances.
Martin Bergström