web-dev-qa-db-fra.com

SQL Server revient à un plan inefficace (analyse d'index en cluster) une fois par semaine

J'ai une requête très simple:

INSERT INTO #tmptbl
SELECT TOP 50 CommentID --this is primary key
FROM Comments WITH(NOLOCK)
WHERE UserID=@UserID
ORDER BY CommentID DESC

contre ce tableau:

CREATE TABLE [dbo].[Comments] (
    [CommentID] int IDENTITY (1, 1) NOT NULL PRIMARY KEY,
    [CommentDate] datetime NOT NULL DEFAULT (getdate()),
    [UserID] int NULL ,
    [Body] nvarchar(max) NOT NULL,
--a couple of other int and bit cols, no indexes on them
)

J'ai un index simple sur la colonne UserID (pas de cols inclus) et tout fonctionne très bien et super rapide.

Mais une fois tous les 5-8 jours, je vois des délais d'attente dans cette partie de l'application. Je vais donc enquêter dans le magasin de requêtes et je vois que le serveur cesse d'utiliser mon index et revient à une stupide "analyse en cluster". La suppression de la table temporaire n'aide pas.

why, Gosh, why???

Afin de résoudre ce problème - je réinitialise le cache du plan pour cette requête particulière (juste pour mémoire, voici comment je fais cela)

select plan_handle FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text (qs.[sql_handle]) AS qt
where text like '%SELECT TOP 50 CommentID FROM hdComments%'
--blahblahblah skipped some code
DBCC FREEPROCCACHE (@plan_handle)

Et puis recommence à fonctionner normalement.

Plans d'exécution: lentrapide

Ça fait des jours que je me gratte la tête ... Des idées?

4
jitbit

Votre index sur UserID n'est pas optimal pour cette requête. Il laisse à l'optimiseur le choix de l'utiliser et d'avoir besoin d'un tri supplémentaire par CommentID ou de parcourir la table (en arrière) pour obtenir les lignes déjà triées par commentID et filtrées à la volée par la clause where et l'opérateur supérieur. Bien que la colonne PK en cluster soit incluse dans chaque colonne non clusterisée, elle est tout comme le pointeur et ne peut donc pas être utilisée pour le tri.

La meilleure façon de l'éviter pour une requête critique comme vous le décrivez est de fournir un index optimal, de sorte que l'optimiseur le choisira probablement à chaque fois. Sur la base des informations que vous avez fournies, votre index doit être un index composite non clusterisé sur (UserID, CommentID DESC) Cela permettra à la fois un accès direct aux lignes des utilisateurs, et également les 50 premières lignes peuvent être analysées dans l'ordre de CommentID, le laissant optimal choix, indépendamment des statistiques et de la sélectivité.

Le serveur SQL est suffisamment intelligent pour le réaliser. Essayez-le ... HTH

3
SQLRaptor

Si la réponse de SQLRaptor ne fonctionne pas pour vous, une autre chose drastique que vous pouvez essayer est d'utiliser l'indicateur de requête FORCESEEK. Cela oblige essentiellement l'optimiseur à toujours utiliser un plan qui effectue une recherche d'index au lieu d'une analyse d'index (si possible).

L'une des raisons pour lesquelles ce n'est pas un premier choix est parce qu'il limite le nombre de plans de requête que l'optimiseur peut choisir d'utiliser et, dans certains cas, commettra une erreur en disant qu'aucun plan n'est disponible pour cet indice de requête. En règle générale, les conseils de requête sont plus un correctif de dernier recours (sauf dans des cas spécifiques d'Edge) mais sans doute moins drastiques que l'exécution de la commande DBCC FREEPROCCACHE et probablement moins drastiques que de toujours recompiler la requête avec le conseil OPTION RECOMPILE de la réponse de KumarHarsh.

(Cela a fini par être la meilleure solution pour un scénario particulier que j'ai récemment rencontré avec une table qui avait des milliards d'enregistrements et l'optimiseur essayait toujours d'utiliser une analyse d'index en cluster, mais il y avait un index non cluster qui était plus applicable à la requête et était en fait toujours plus rapide comme recherche.)

Consultez la section FORCESEEK du document Microsoft pour plus d'informations: https://docs.Microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql- server-ver15

4
J.D.

J'ai déjà vu cela sur un système mal conçu où la clé primaire était un composé et une colonne de celui-ci a été mise à jour assez fréquemment le jour de sa création. Cela a conduit à la fragmentation de l'index le soir (c'était un système 24h/24 et 7j/7) heures avant la planification des reconstructions d'index. À ce stade, SQL a cessé d'utiliser la meilleure requête et a considérablement ralenti, même si la requête utilisant le PK réel était encore plus rapide. Lorsque les index ont été reconstruits, bien sûr, SQL est revenu au plan de requête sain.

Une solution a été de donner à la requête une indication d'index. Vous pouvez le faire en:

select …..
from tableA a with(index(pk_tableA)) -- any table index allowed
inner join tableB b on b.Id = a.BId
where etc etc

Cela ne semblait pas être une bonne solution - idéalement, nous aurions repensé la table et la façon dont elle était utilisée - mais des budgets.

Remarque (comme Jonathon me le rappelle dans les commentaires) L'index a été reconstruit en ligne plutôt que hors ligne. Cela doit être spécifié dans la commande de reconstruction.

Ci-dessous de MSSqlTips.com

ALTER INDEX [IX_Test] ON [dbo].[Test] REBUILD WITH (ONLINE = ON);

Cette option n'est pas disponible si:

  • l'index est un index XML
  • l'index est un index spatial
  • l'index est sur une table temporaire locale
  • l'index est groupé et la table contient une ou des colonnes de base de données LOB
  • l'index n'est pas groupé et l'index lui-même contient une ou des colonnes de base de données LOB

De plus, comme le mentionne Denis Rubashkin, l'option en ligne n'est disponible que dans la version d'entreprise de SQL Server.

0
simon at rcl