web-dev-qa-db-fra.com

L'opérateur de bobine impatiente est-il utile pour cette suppression d'une colonne en clustere?

Je teste la suppression des données d'un indice de colonne en cluster.

J'ai remarqué qu'il existe un grand opérateur de bobine adressé dans le plan d'exécution:

enter image description here

Ceci est terminé avec les caractéristiques suivantes:

  • 60 millions de lignes supprimées
  • 1.9 GiB TEMPDB utilisé
  • Temps d'exécution de 14 minutes
  • Plan de série
  • 1 rebind à la bobine
  • Coût estimé pour numériser: 364.821

Si j'attends l'estimateur sous-estimé, je reçois un plan plus rapide qui évite l'utilisation de Tempdb:

enter image description here

Coût estimé du balayage: 56.901

(Il s'agit d'un plan estimé, mais les chiffres des commentaires sont corrects.)

Fait intéressant, la bobine disparaît à nouveau si je rafraîchis les magasins Delta en exécutant ce qui suit:

ALTER INDEX IX_Clustered ON Fact.RecordedMetricsDetail REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);

La spoule semble être introduite uniquement lorsqu'il y a plus que certains seuils de pages dans les magasins Delta.

Pour vérifier la taille des magasins Delta, j'exécute la requête suivante pour vérifier les pages à ligne pour la table:

SELECT  
  SUM([in_row_used_page_count]) AS in_row_used_pages,
  SUM(in_row_data_page_count) AS in_row_data_pages
FROM sys.[dm_db_partition_stats] as pstats
JOIN sys.partitions AS p
ON pstats.partition_id = p.partition_id
WHERE p.[object_id] = OBJECT_ID('Fact.RecordedMetricsDetail');

Y a-t-il un avantage plausible pour l'itérateur de la bobine dans le premier plan? Je dois supposer que cela est conçu comme une amélioration de la performance et non pour une protection d'Halloween, car sa présence n'est pas cohérente.

Je le teste sur 2016 CTP 3.1, mais je vois le même comportement sur 2014 SP1 CU3.

J'ai posté un script qui génère le schéma et les données et vous promène en démontrant le problème ici .

La question est la question principalement de la curiosité du comportement de l'optimiseur à ce stade, car j'ai une solution de contournement pour la question qui a motivé la question (une importante spool remplie TEMPDB). Je supprime maintenant à l'aide de la commutation de partition à la place.

28
James L

Y a-t-il un avantage plausible à l'itérateur de la bobine dans le premier plan ?

Cela dépend de ce que vous considérez comme "plausible", mais la réponse en fonction du modèle de coût est oui. Bien sûr, cela est vrai, car l'optimiseur choisit toujours le plan le moins cher qu'il trouve.

La vraie question est pourquoi Le modèle de coût considère le plan avec la bobine tellement moins cher que le plan sans. Considérons des plans estimés créés pour une table fraîche (de votre script) avant que toutes les lignes ne soient ajoutées au magasin Delta:

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE);

Le coût estimé pour ce plan est une énorme 771 734 unités :

Original plan

Le coût est presque tous associé à l'indice en regroupement supprimé, car les suppressions devraient entraîner une grande partie des E/S aléatoires. Ceci est juste la logique générique qui s'applique à toutes les modifications de données. Par exemple, un ensemble de modifications non ordonnées à un indice B-Tree est supposé entraîner des E/S largement aléatoires, avec un coût d'E/S associé.

Les plans de modification de données peuvent présenter une sorte pour présenter des lignes dans un ordre qui favorisera l'accès séquentiel, pour ces raisons de coût. L'impact est exacerbé dans ce cas car la table est partitionnée. Très partitionné, en fait; Votre script crée 15 000 d'entre eux. Les mises à jour aléatoires sur une table très partitionnée sont calculées particulièrement élevées car le prix pour commuter les cloisons (ROWSETS) est également donné à un coût élevé.

Le dernier facteur majeur à prendre en compte est que la requête de mise à jour simple ci-dessus (où "mise à jour" désigne toute opération de changement de données, y compris une suppression) qualifiée pour une optimisation appelée "partage des rowset", où le même rowet interne est utilisé pour la numérisation et Mise à jour de la table. Le plan d'exécution affiche toujours deux opérateurs distincts, mais néanmoins, il n'y a qu'un seul rangé utilisé.

Je le mentionne, car être capable d'appliquer cet optimisation signifie que l'optimiseur prend un chemin de code qui ne considère simplement pas les avantages potentiels de explicitement Tri pour réduire le coût des E/S aléatoires. Lorsque la table est un arbre B, cela a du sens, car la structure est intrinsèquement commandée, le partage de la rowet fournit automatiquement tous les avantages potentiels.

La conséquence importante est que la logique des coûts de l'opérateur de mise à jour ne considère pas cette prestation de commande (favorisant les E/S séquentielles ou d'autres optimisations) où l'objet sous-jacent est la colonne Store. En effet, les modifications de la colonne Store ne sont pas effectuées en place; Ils utilisent un magasin delta. Le modèle de coût reflète donc une différence entre les mises à jour des rowset partagées sur les arbres B par rapport aux colonnes.

Néanmoins, dans le cas particulier d'une colonne (très!) De colonne partitionnée, il pourrait toujours y avoir un avantage à la commande préservée, dans la mesure de toutes les mises à jour d'une partition avant de passer à la suivante, peut encore être avantageuse d'un point de vue d'E/S .

La logique de coûts standard est réutilisée pour les magasins de colonnes ici, un plan qui préserve la commande de partition (bien que non ordonnée dans chaque partition) est moins coûteux. Nous pouvons voir cela sur la requête de test en utilisant un drapeau de trace non documenté 2332 pour exiger une entrée triée sur l'opérateur de mise à jour. Cela définit la propriété DMLRequestSort sur true à la mise à jour et oblige l'optimiseur à produire un plan qui fournit toutes les lignes pour une partition avant de passer à la suivante:

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332);

Le coût estimé pour ce plan est très inférieur, à 52.5174 unités:

DMLRequestSort=true plan

Cette réduction du coût est dû au coût d'E/S moins élevé à la mise à jour. La bobine introduite n'effectue aucune fonction utile, sauf qu'il peut garantir la sortie dans l'ordre de partition, comme requis par la mise à jour avec DMLRequestSort = true (la numérisation série d'un index de stockage de colonne ne peut pas fournir cette garantie). Le coût de la bobine elle-même est considéré comme relativement faible, notamment par rapport à la réduction du coût (probablement irréaliste) à la mise à jour.

La décision sur l'opportunité d'avoir besoin d'une entrée commandée à l'opérateur de mise à jour est rendue très tôt dans l'optimisation de la requête. Les heuristiques utilisées dans cette décision n'ont jamais été documentées, mais peuvent être déterminées par procès et par erreur. Il semble que la taille des magasins Delta est une contribution à cette décision. Une fois fait, le choix est permanent pour la compilation de la requête. Non USE PLANHint réussira: la cible du plan a été commandée à la mise à jour, soit pas.

Il existe une autre façon d'obtenir un plan à faible coût pour cette requête sans limiter artificiellement l'estimation de la cardinalité. Une estimation suffisamment basse pour éviter que la bobine entraînera probablement de la fausse dmlrequestsTReTReTs, ce qui entraîne un coût de plan estimé très élevé en raison des E/S aléatoires attendus. Une alternative consiste à utiliser le drapeau de trace 8649 (plan parallèle) en conjonction avec 2332 (dmlrequestsort = true):

DELETE Fact.RecordedMetricsDetail
WHERE MeasurementTime < DATEADD(day,-1,GETUTCDATE())
OPTION (RECOMPILE, QUERYTRACEON 2332, QUERYTRACEON 8649);

Il en résulte un plan qui utilise un scan parallèle en mode par partition et une préservation de commande (fusion) Rassembler des flux échange:

Ordered Delete

En fonction de l'efficacité du temps d'exécution de la commande de partition sur votre matériel, cela peut accomplir le meilleur des trois. Cela dit, de grandes modifications ne sont pas une bonne idée du magasin de colonnes. L'idée de la commutation de partition est donc presque bien meilleure. Si vous pouvez faire face aux longues périodes de compilation et les choix de planiers d'omniprésents souvent observés avec des objets partitionnés - en particulier lorsque le nombre de partitions est grande.

Combinant de nombreuses caractéristiques, relativement nouvelles, surtout près de leurs limites, est un excellent moyen d'obtenir de mauvais projets d'exécution. La profondeur du support d'optimisation a tendance à s'améliorer au fil du temps, mais utiliser 15 000 partitions de colonne signifie probablement toujours que vous vivrez dans des moments intéressants.

23
Paul White 9