web-dev-qa-db-fra.com

SQL Server 2016 Bad Query Plan verrouille DB une fois par semaine

Une fois par semaine, au cours des 5 dernières semaines, à peu près au même moment de la journée (tôt le matin, peut être basé sur l'activité des utilisateurs lorsque les gens commencent à l'utiliser), SQL Server 2016 (AWS RDS, en miroir) commence à expirer beaucoup de requêtes.

METTRE À JOUR LES STATISTIQUES sur toutes les tables le corrige toujours immédiatement.

Après la première fois, je l'ai mis à jour toutes les statistiques sur toutes les tables tous les soirs (au lieu d'hebdomadaires), mais cela s'est toujours produit (environ 8 heures après la mise à jour des statistiques, mais pas tous les jours).

Cette dernière fois, j'ai activé Query Store pour voir si je pouvais trouver de quelle requête/plan de requête spécifique il s'agissait. Je pense que j'ai pu le réduire à un:

Bad query plan

Après avoir trouvé cette requête, j'ai ajouté un index recommandé qui manquait à cette requête peu utilisée (mais qui touche beaucoup de tables fréquemment utilisées).

Le mauvais plan de requête effectuait une analyse d'index (sur une table avec seulement 10 000 lignes). D'autres plans de requête qui sont retournés en millisecondes, faisaient cependant la même analyse. Le plan de requête le plus récent, après avoir créé le nouvel index, ne cherche que. Mais même sans cet indice, 99% du temps, il revenait en quelques millisecondes, mais ensuite, chaque semaine, cela prendrait> 40 secondes.

Cela a commencé à se produire après le passage à SQL Server 2016 à partir de 2012.

DBCC CHECKDB ne renvoie aucune erreur.

  1. Le nouvel index résoudra-t-il le problème, ce qui l'empêchera-t-il de choisir à nouveau le mauvais plan?
  2. Dois-je "forcer" le plan qui fonctionne bien maintenant?
  3. Comment m'assurer que cela n'arrive pas à une autre requête/plan?
  4. Est-ce le symptôme d'un problème plus important?

Index que je viens d'ajouter:

CREATE NONCLUSTERED INDEX idx_AppointmetnAttendee_AttendeeType
ON [dbo].[AppointmentAttendee] ([UserID],[AttendeeType])

CREATE NONCLUSTERED INDEX [idx_appointment_start] ON [dbo].[Appointment]
(
    [ProjectID] ASC,
    [Start] ASC
)
INCLUDE (   [ID],
    [AllDay],
    [End],
    [Location],
    [Notes],
    [Title],
    [CreatedByID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

Texte complet de la requête:

https://Pastebin.com/Z5szPBf (généré par LINQ, je peux/devrait être en mesure d'optimiser les colonnes sélectionnées, mais cela ne devrait pas être pertinent pour ce problème)

Je vais répondre à vos questions dans un ordre différent de celui que vous leur avez posé.

4. Est-ce le symptôme d'un problème plus important?

nouvel estimateur de cardinalité dans SQL Server 2016 pourrait contribuer au problème. SQL Server 2012 utilise l'héritage CE et vous n'avez rencontré aucun problème avec cette version. Le nouvel estimateur de cardinalité émet différentes hypothèses sur vos données et peut générer différents plans de requête pour le même SQL. Vous pouvez bénéficier de meilleures performances pour certaines requêtes avec l'ancien CE en fonction de votre requête et de vos données. Par conséquent, certaines parties de votre modèle de données peuvent ne pas correspondre le mieux au nouveau CE. Ce n'est pas grave, mais vous devrez peut-être contourner le nouveau CE pour l'instant.

Je serais également préoccupé par les performances de requête incohérentes, même avec les mises à jour quotidiennes des statistiques. Une chose importante à noter est que la collecte de statistiques sur toutes les tables effacera efficacement tous les plans de requête du cache, de sorte que vous pourriez avoir un problème avec les statistiques ou avec le reniflement des paramètres. Il est difficile de faire une détermination sans beaucoup d'informations sur votre modèle de données, le taux de modification des données, les politiques de mise à jour des statistiques, la façon dont vous appelez votre code, etc. SQL Server 2016 offre certains paramètres de niveau de base de données pour le reniflement des paramètres ce qui pourrait être utile, mais cela pourrait affecter l'ensemble de votre application au lieu d'une seule requête problématique.

Je vais jeter un exemple de scénario qui pourrait conduire à ce comportement. Tu as dit:

Certains utilisateurs peuvent avoir 1 enregistrement d'autorisation, certains jusqu'à 20 000.

Supposons que vous collectiez des statistiques sur toutes les tables, ce qui efface tous les plans de requête. En fonction des facteurs mentionnés ci-dessus, si la première requête de la journée concerne un utilisateur avec seulement 1 enregistrement d'autorisation, SQL Server peut mettre en cache un plan qui fonctionne bien pour les utilisateurs avec 1 enregistrement mais fonctionne très bien avec les utilisateurs avec 20k enregistrements. Si la première requête de la journée concerne un utilisateur avec 20 000 enregistrements, vous pouvez obtenir un bon plan pour 20 000 enregistrements. Lorsque le code est exécuté sur un utilisateur avec 1 enregistrement, ce n'est peut-être pas la requête la plus optimale, mais elle peut quand même se terminer en ms. Cela ressemble vraiment à un reniflement de paramètre. Cela explique pourquoi vous ne voyez pas toujours le problème ou pourquoi il faut parfois des heures pour apparaître.

1. Le nouvel index résoudra-t-il le problème, ce qui l'empêchera-t-il de choisir à nouveau le mauvais plan?

Je pense que l'un des index que vous avez ajoutés évitera le problème car l'accès aux données requises via l'index coûtera moins cher que d'effectuer une analyse d'index en cluster sur la table, en particulier lorsque l'analyse ne peut pas se terminer tôt. Zoomons sur la mauvaise partie du plan de requête:

bad query plan

SQL Server estime qu'une seule ligne sera renvoyée par la jointure le [Permission] et [Project]. Pour chaque ligne de l'entrée externe, il effectuera une analyse d'index en cluster sur [Appointment]. Toutes les lignes seront analysées à partir de ce tableau, mais uniquement celles correspondant au filtrage sur [Start] sera retourné à l'opérateur de jointure. Au sein de l'opérateur de jointure, les résultats sont encore réduits.

Le plan de requête décrit ci-dessus peut être correct s'il n'y a vraiment qu'une seule ligne envoyée à l'entrée externe de la jointure. Cependant, si l'estimation de cardinalité de la jointure est incorrecte et que nous obtenons, disons, 1 000 lignes, SQL Server effectuera 1 000 analyses d'index en cluster sur [Appointment]. Les performances du plan de requête sont très sensibles aux problèmes d'estimation.

Le moyen le plus direct de ne plus jamais obtenir ce plan de requête serait de créer un index de couverture par rapport au [Appointment] table. Quelque chose comme un index sur [ProjectId] et [Start] devrait le faire. Il semble que ce soit exactement le [idx_appointment_start] index que vous avez créé pour résoudre le problème. Une autre façon de décourager SQL Server de choisir le plan de requête consiste à corriger l'estimation de cardinalité de la jointure sur [Permission] et [Project]. Les moyens typiques de le faire incluent la modification du code, la mise à jour des statistiques, l'utilisation de l'héritage CE, la création de statistiques multi-colonnes, la fourniture de plus d'informations sur les variables locales à SQL Server, comme avec un indice RECOMPILE, ou la matérialisation de ces lignes dans un table temporaire. Beaucoup de ces techniques ne sont pas une bonne approche lorsque vous avez besoin d'un temps de réponse de niveau ms ou devez écrire du code via un ORM.

L'index que vous avez créé sur [AppointmentAttendee] n'est pas un moyen direct de résoudre le problème. Cependant, vous obtiendrez des statistiques multi-colonnes sur l'index et ces statistiques pourraient décourager le mauvais plan de requête. L'index peut fournir un moyen plus efficace d'accéder aux données, ce qui peut également décourager le mauvais plan de requête, mais je ne pense pas qu'il existe une quelconque garantie que cela ne se reproduira pas uniquement avec l'index sur [AppointmentAttendee].

3. Comment puis-je m'assurer que cela n'arrive pas à une autre requête/plan?

Je comprends pourquoi vous posez cette question, mais elle est extrêmement large. Mon seul conseil est d'essayer de mieux comprendre la cause première de l'instabilité du plan de requête, de valider que vous avez les bons index créés pour votre charge de travail et de tester et de surveiller soigneusement votre charge de travail. Microsoft a quelques conseils généraux sur la façon de gérer les régressions du plan de requête causées par le nouveau CE dans SQL Server 2016:

Le flux de travail recommandé pour la mise à niveau du processeur de requêtes vers la dernière version du code est le suivant:

  1. Mettre à niveau une base de données vers SQL Server 2016 sans modifier le niveau de compatibilité de la base de données (conservez-le au niveau précédent)

  2. Activez le magasin de requêtes sur la base de données. Pour plus d'informations sur l'activation et l'utilisation du magasin de requêtes, voir Surveillance des performances à l'aide du magasin de requêtes.

  3. Attendez suffisamment de temps pour collecter des données représentatives de la charge de travail.

  4. Changez le niveau de compatibilité de la base de données à 130

  5. À l'aide de SQL Server Management Studio, évaluez s'il existe des régressions de performances sur des requêtes spécifiques après la modification du niveau de compatibilité

  6. Pour les cas où il y a des régressions, forcez le plan précédent dans le magasin de requêtes.

  7. S'il existe des plans de requête qui ne parviennent pas à forcer ou si les performances sont toujours insuffisantes, pensez à rétablir le niveau de compatibilité au paramètre précédent, puis à engager le support client Microsoft.

Je ne dis pas que vous devez rétrograder vers SQL Server 2012 et recommencer, mais la technique générale décrite peut vous être utile.

2. Dois-je "forcer" le plan qui fonctionne bien maintenant?

Cela dépend entièrement de vous. Si vous pensez que vous disposez d'un plan de requête qui fonctionne bien pour tous les paramètres d'entrée possibles, que vous êtes à l'aise avec la fonctionnalité du magasin de requêtes et que vous souhaitez la tranquillité d'esprit associée au forçage d'un plan de requête, allez-y. Forcer les plans de requête qui avaient des régressions fait partie de la politique de mise à niveau recommandée par Microsoft pour SQL Server 2016 après tout.

16
Joe Obbish