web-dev-qa-db-fra.com

Query T-SQL utilisant un plan complètement différent en fonction du nombre de lignes que je mettez à jour

J'ai une déclaration de mise à jour SQL avec une clause "Top (x)" et la ligne de mise à jour des valeurs d'environ 4 milliards de lignes. Lorsque j'utilise "haut (10)", je reçois un plan d'exécution qui exécute presque instantanément, mais lorsque j'utilise "Top (50)" ou plus grand, la requête jamais (au moins, pas pendant que j'attendie) finition, et Il utilise un plan d'exécution complètement différent. La plus petite requête utilise un plan très simple avec une paire de vestiaires d'index et une jointure à boucle imbriquée, où la même requête est exacte (avec un nombre différent de lignes dans la clause supérieure de l'instruction de mise à jour) utilise un plan impliquant deux index différents. , une bobine de table, un parallélisme et un tas d'une autre complexité.

J'ai utilisé "Option (Plan d'utilisation ...)" Pour le forcer à utiliser le plan d'exécution généré par la plus petite requête - lorsque je le fais, je peux mettre à jour jusqu'à 100 000 lignes en quelques secondes. Je sais que le plan de requête est bon, mais SQL Server choisira que le plan à part entière lorsque seul un petit nombre de lignes sont impliqués - tout nombre décemment grand rang dans ma mise à jour entraînera le plan sous-optimal.

Je pensais que le parallélisme pourrait être à blâmer, alors j'ai défini MAXDOP 1 Sur la requête, mais à aucun effet - cette étape est partie, mais le mauvais choix/la performance n'est pas. J'ai aussi couru sp_updatestats Juste ce matin pour que ce n'était pas la cause.

J'ai joint les deux plans d'exécution - le plus court est aussi le plus rapide. En outre, voici la requête en question (il convient de noter que le choix que j'ai inclus semble être rapide dans les cas de comptes de petite et grande rangée):

    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

Voici le plan rapideQuick Execution Plan

et voici le plus lentSlow Execution Plan

Y a-t-il quelque chose d'évident dans la manière dont je suis en train de mettre en place ma requête ou dans le plan d'exécution fourni à condition de se prêter au mauvais choix que le moteur de requête fait? Si nécessaire, je peux également inclure les définitions de table impliquées et les index qui sont définis sur eux.

à ceux qui ont demandé une version statistiques uniquement des objets de base de données: Je ne me suis même pas réalisé que vous pouviez le faire, mais cela a un sens complet! J'ai essayé de générer les scripts pour une base de données Statistiques uniquement pour que d'autres puissent tester les plans d'exécution pour eux-mêmes, mais je peux générer des statistiques/histogrammes générer sur mon index filtré (erreur de syntaxe dans le script, il semble que je suis donc mal 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 sur une chasse à l'oie.

Mise à jour et quelques plans d'exécution plus complètes: Tout d'abord, Explorateur de plan de SQL Sentry est un outil incroyable. Je ne savais même pas qu'il existait avant de consulter les autres questions de plan de requête sur ce site, et cela avait un peu à dire sur la manière dont mes requêtes exécutaient. Bien que je ne sois pas sûr comment aborder le problème, ils ont rendu évident quel est le problème.

Voici le résumé des 10, 100 et 1000 lignes - vous pouvez voir que la requête de 1000 rangées est une voie, sans relâche avec les autres: Statement Summary

Vous pouvez voir que la troisième requête a un nombre de lectures ridicules, de sorte que cela fait évidemment quelque chose de complètement différent. Voici le plan d'exécution estimé, avec des chiffres de rangée. plan d'exécution estimée de 1000 lignes : 1000-row estimated execution plan

Et voici les résultats réels du plan d'exécution (d'ailleurs, par "Jamais finis", il s'avère que je voulais dire "finition en une heure"). plan d'exécution réel de 1000 lignes 1000-row actual execution plan

La première chose que j'ai remarquée, c'est que, au lieu de tirer des rangées de 60 000 de la table de dim-tige comme elle s'attend, elle tire réellement 1,6 milliard, avec un B . En regardant ma requête, je ne suis pas sûr de la façon dont il retire que de nombreuses lignes de la table Dimtime. L'opérateur que j'utilise, vous permet de vous assurer que je tire le bon enregistrement de #mac en fonction du compte rendu de la table de fait. Cependant, lorsque j'ajoute une ligne à la clause où je filtre T.timevalue (ou T.timeid) à une seule valeur, je peux mettre à jour avec succès 100 000 lignes en quelques secondes. À la suite de cela, et comme indiqué clairement dans les plans d'exécution, j'ai inclus, il est évident que c'est mon temps que ma table est le problème, mais je ne sais pas comment je modifierais les critères de jointure pour contourner ce problème et maintenir la précision . Des pensées?

Pour référence, ici le plan (avec des rangs de ligne) pour la mise à jour de 100 lignes. Vous pouvez voir qu'il frappe le même index et toujours avec une tonne de lignes, mais nulle part près de la même ampleur d'un problème. EXÉCUTION DE 100 ROUTE AVEC NOMBRE DE LA RIGHTenter image description here

20
SqlRyan

L'index sur Dimtime change. Le plan plus rapide utilise un indice _DTA. Tout d'abord, assurez-vous que ce n'est pas marqué comme un indice hypothétique dans les sys.indexes.

Pensant que vous pourriez contourner un paramétrage en utilisant la table #mac à filtrer au lieu de simplement fournir les dates de début/fin comme celle-ci où T.timevalue entre @Stardate et @enddate. Débarrassez-vous de cette table Temp.

3
william_a_dba

Sans plus d'informations sur la ligne compte dans le plan, ma recommandation préliminaire est d'organiser la commande de jointure correcte dans la requête et de la forcer à l'aide de OPTION (FORCE ORDER). Appliquer l'ordre de participation du premier plan.

1
usr