J'ai une instruction SQL UPDATE avec une clause "TOP (X)", et la ligne dans laquelle je mets à jour les valeurs contient environ 4 milliards de lignes. Lorsque j'utilise "TOP (10)", j'obtiens un plan d'exécution qui s'exécute presque instantanément, mais lorsque j'utilise "TOP (50)" ou plus, la requête ne se termine jamais (du moins, pas en attendant), et il utilise un plan d'exécution complètement différent. La requête plus petite utilise un plan très simple avec une paire de recherches d'index et une jointure en boucle imbriquée, où la même requête exacte (avec un nombre différent de lignes dans la clause TOP de l'instruction UPDATE) utilise un plan qui implique deux recherches d'index différentes , une bobine de table, le parallélisme et un tas d'autres complexités.
J'ai utilisé "OPTION (USE PLAN ...)" pour le forcer à utiliser le plan d'exécution généré par la requête plus petite - lorsque je fais cela, je peux mettre à jour jusqu'à 100 000 lignes en quelques secondes. Je sais que le plan de requête est bon, mais SQL Server ne choisira ce plan que lorsqu'il ne s'agit que d'un petit nombre de lignes - tout nombre de lignes décemment élevé dans ma mise à jour se traduira par un plan sous-optimal.
Je pensais que le parallélisme était peut-être à blâmer, alors j'ai mis MAXDOP 1
la requête, mais en vain - cette étape a disparu, mais le mauvais choix / performances ne l'est pas. J'ai également couru sp_updatestats
ce matin pour m'assurer que ce n'était pas la cause.
J'ai joint les deux plans d'exécution - le plus court est aussi le plus rapide. De plus, voici la requête en question (il convient de noter que le SELECT que j'ai inclus semble être rapide dans les cas de petits et de grands nombres de lignes):
update top (10000) FactSubscriberUsage3
set AccountID = sma.CustomerID
--select top 50 f.AccountID, sma.CustomerID
from FactSubscriberUsage3 f
join dimTime t
on f.TimeID = t.TimeID
join #mac sma
on f.macid = sma.macid
and t.TimeValue between sma.StartDate and sma.enddate
where f.AccountID = 0 --There's a filtered index on the table for this
Y a-t-il quelque chose d'évident dans la façon dont je configure ma requête ou dans le plan d'exécution à condition qu'il se prête au mauvais choix que fait le moteur de requête? Si nécessaire, je peux également inclure les définitions de table impliquées et les index qui y sont définis.
Pour ceux qui ont demandé une version uniquement statistique des objets de la base de données: je ne savais même pas que vous pouviez le faire, mais c'est tout à fait logique! J'ai essayé de générer les scripts pour une base de données uniquement pour que les autres puissent tester les plans d'exécution par eux-mêmes, mais je peux générer des statistiques / histogrammes sur mon index filtré (erreur de syntaxe dans le script, semble-t-il), donc je suis pas de chance là-bas. J'ai essayé de supprimer le filtre et les plans de requête étaient proches, mais pas exactement les mêmes, et je ne veux envoyer personne à une chasse aux oies.
Mise à jour et quelques plans d'exécution plus complets: Tout d'abord, l'Explorateur de plans de SQL Sentry est un outil incroyable. Je ne savais même pas qu'il existait avant de voir les autres questions sur le plan de requête sur ce site, et il avait beaucoup à dire sur la façon dont mes requêtes s'exécutaient. Bien que je ne sois pas sûr de la façon de résoudre le problème, ils ont rendu évident le problème.
Voici le résumé pour 10, 100 et 1000 lignes - vous pouvez voir que la requête de 1000 lignes est très différente des autres:
Vous pouvez voir que la troisième requête a un nombre ridicule de lectures, donc elle fait évidemment quelque chose de complètement différent. Voici le plan d'exécution estimé, avec le nombre de lignes. Plan d'exécution estimé à 1000 lignes:
Et voici les résultats réels du plan d'exécution (en passant, par "ne finit jamais", il s'avère que je voulais dire "finit en une heure"). Plan d'exécution réel à 1 000 lignes
La première chose que je remarque est que, au lieu de tirer 60K lignes de la table DimTime comme il attend, il est en fait tirer 1,6 milliard, avec un B . En regardant ma requête, je ne sais pas comment il retire autant de lignes de la table dimTime. L'opérateur BETWEEN que j'utilise garantit simplement que je tire le bon enregistrement de #mac en fonction de l'enregistrement de temps dans la table de faits. Cependant, lorsque j'ajoute une ligne à la clause WHERE où je filtre t.TimeValue (ou t.TimeID) sur une seule valeur, je peux mettre à jour 100 000 lignes en quelques secondes. En conséquence, et comme indiqué clairement dans les plans d'exécution que j'ai inclus, il est évident que c'est mon calendrier qui pose problème, mais je ne sais pas comment je changerais les critères de jointure pour contourner ce problème et maintenir la précision . Des pensées?
Pour référence, voici le plan (avec le nombre de lignes) pour la mise à jour de 100 lignes. Vous pouvez voir qu'il atteint le même index, et toujours avec une tonne de lignes, mais loin de la même ampleur d'un problème. Exécution de 100 lignes avec nombre de lignes :
from #mac sma join f on f.macid = sma.macid join dimTime t on f.TimeID = t.TimeID and t.TimeValue between sma.StartDate and sma.enddate
vsfrom #mac join t on t.TimeValue between sma.StartDate and sma.enddate join f on f.TimeID = t.TimeID and f.macid = sma.macid
TOP 50
devrait toujours s'exécuter rapidement. Pouvez-vous télécharger les plans XML? Je dois regarder le nombre de lignes. Pouvez-vous exécuter le TOP 50
avec maxdop 1 et en tant que sélection, pas en tant que mise à jour et publier le plan? (Essayer de simplifier / diviser l'espace de recherche).
t.TimeValue between sma.StartDate and sma.enddate
pourrait finir par générer beaucoup plus de lignes inutiles qui seront ensuite filtrées dans la jointure contre FactSubscriber et ne se retrouveront donc pas dans le résultat final.
sp_updatestatistics
sur la table?