L'opérateur de spouleur désireux est-il utile pour cette suppression d'un magasin de colonnes en cluster?


28

Je teste la suppression de données d'un index columnstore en cluster.

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

entrez la description de l'image ici

Cela se termine avec les caractéristiques suivantes:

  • 60 millions de lignes supprimées
  • 1,9 Gio TempDB utilisé
  • 14 minutes d'exécution
  • Plan série
  • 1 reliure sur bobine
  • Coût estimé pour la numérisation: 364.821

Si je trompe l'estimateur en le sous-estimant, j'obtiens un plan plus rapide qui évite d'utiliser TempDB:

entrez la description de l'image ici

Estimation du coût de l'analyse: 56.901

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

Fait intéressant, le spool disparaît à nouveau si je vide les magasins delta en exécutant ce qui suit:

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

Le spouleur ne semble être introduit que lorsqu'il y a plus d'un certain seuil 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 en ligne de 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 bobine dans le premier plan? Je dois supposer qu'il est destiné à améliorer les performances et non à protéger Halloween car sa présence n'est pas cohérente.

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

J'ai publié un script qui génère un schéma et des données et vous explique comment résoudre le problème ici .

La question est principalement par curiosité sur le comportement de l'optimiseur à ce stade car j'ai une solution de contournement pour le problème qui a provoqué la question (une grande TempDB remplie de spool). Je supprime maintenant en utilisant la commutation de partition à la place.


2
Si j'essaie, OPTION (QUERYRULEOFF EnforceHPandAccCard)la bobine disparaît. Je suppose que HP pourrait être "Halloween Protection". Cependant, alors essayer d'utiliser ce plan avec un USE PLANindice échoue (tout comme essayer d'utiliser le plan à partir de la OPTIMIZE FOR solution de contournement aussi)
Martin Smith

Merci @MartinSmith. Une idée de ce AccCardque ce serait? Colonne ascendante cardinalité cardinalité peut-être?
James L

1
@JamesLupolt Non, je n'ai rien trouvé de particulièrement convaincant pour moi. Peut-être que l'Acc est accumulé ou accessible?
Martin Smith

Réponses:


22

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

Cela dépend de ce que vous considérez comme «plausible», mais la réponse selon le 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 de savoir pourquoi le modèle de coût considère le plan avec la bobine tellement moins cher que le plan sans. Considérez les plans estimés créés pour une nouvelle table (à partir de votre script) avant d'ajouter des lignes au magasin delta:

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

Le coût estimé de ce plan est de 771 734 unités :

Plan d'origine

Le coût est presque entièrement associé à la suppression d'index en cluster, car les suppressions devraient entraîner de nombreuses E / S aléatoires. C'est juste la logique générique qui s'applique à toutes les modifications de données. Par exemple, un ensemble non ordonné de modifications d'un index b-tree est supposé entraîner des E / S largement aléatoires, avec un coût d'E / S élevé associé.

Les plans de modification des données peuvent comporter un tri pour présenter les lignes dans un ordre qui favorisera l'accès séquentiel, pour exactement 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 en crée 15 000. Les mises à jour aléatoires d'une table très partitionnée sont particulièrement coûteuses, car le prix du basculement de partitions (ensembles de lignes) en milieu de flux est également très coûteux.

Le dernier facteur majeur à considérer est que la simple requête de mise à jour ci-dessus (où «mise à jour» signifie toute opération de changement de données, y compris une suppression) remplit les conditions d'une optimisation appelée «partage d'ensemble de lignes», où le même ensemble de lignes interne est utilisé à la fois pour l'analyse et mise à jour de la table. Le plan d'exécution montre toujours deux opérateurs distincts, mais néanmoins, un seul ensemble de lignes est utilisé.

Je mentionne cela parce que pouvoir appliquer cette optimisation signifie que l'optimiseur prend un chemin de code qui ne tient tout simplement pas compte des avantages potentiels d' un tri explicite 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 ordonnée, donc le partage de l'ensemble de lignes fournit automatiquement tous les avantages potentiels.

La conséquence importante est que la logique de calcul des coûts pour l'opérateur de mise à jour ne prend pas en compte cet avantage de classement (promotion d'E / S séquentielles ou d'autres optimisations) lorsque l'objet sous-jacent est le stockage de colonnes. Cela est dû au fait que les modifications du magasin de colonnes ne sont pas effectuées sur place; ils utilisent un magasin delta. Le modèle de coût reflète donc une différence entre les mises à jour des ensembles de lignes partagées sur les arbres b et les magasins de colonnes.

Néanmoins, dans le cas particulier d'un magasin de colonnes partitionné (très!), Il pourrait toujours être avantageux de conserver l'ordre dans la mesure où effectuer toutes les mises à jour d'une partition avant de passer à la suivante pourrait toujours être avantageux du point de vue des E / S. .

La logique de coût standard est réutilisée pour les magasins de colonnes ici, donc un plan qui préserve l'ordre des partitions (mais pas dans chaque partition) est moins cher. Nous pouvons le voir sur la requête de test en utilisant l'indicateur de trace non documenté 2332 pour exiger une entrée triée vers l'opérateur de mise à jour. Cela définit la DMLRequestSortpropriété sur true lors de la mise à jour et force 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é de ce plan est très inférieur, à 52,5174 unités:

DMLRequestSort = vrai plan

Cette réduction des coûts est entièrement due au coût d'E / S estimé inférieur lors de la mise à jour. Le spool introduit n'effectue aucune fonction utile, sauf qu'il peut garantir la sortie dans l'ordre des partitions, comme requis par la mise à jour avec DMLRequestSort = true(l'analyse en série d'un index de stockage de colonnes ne peut pas fournir cette garantie). Le coût de la bobine elle-même est considéré comme relativement faible, en particulier par rapport à la réduction (probablement irréaliste) des coûts lors de la mise à jour.

La décision de demander ou non une entrée ordonnée à l'opérateur de mise à jour est prise très tôt dans l'optimisation des requêtes. Les heuristiques utilisées dans cette décision n'ont jamais été documentées, mais peuvent être déterminées par essais et erreurs. Il semble que la taille des magasins delta soit un élément de cette décision. Une fois fait, le choix est permanent pour la compilation de la requête. Aucun USE PLANindice ne réussira: la cible du plan a soit ordonné l'entrée de la mise à jour, soit elle ne l'a pas fait.

Il existe un autre moyen d'obtenir un plan à faible coût pour cette requête sans limiter artificiellement l'estimation de cardinalité. Une estimation suffisamment basse pour éviter le spool entraînera probablement une erreur sur DMLRequestSort, entraînant un coût de plan estimé très élevé en raison des E / S aléatoires attendues. Une alternative consiste à utiliser l'indicateur 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 une analyse parallèle en mode batch par partition et un échange Gather Streams préservant l'ordre (fusion):

Suppression ordonnée

Selon l'efficacité au moment de l'exécution de la commande de partition sur votre matériel, cela peut fonctionner au mieux des trois. Cela dit, les grandes modifications ne sont pas une bonne idée sur le magasin de colonnes, donc l'idée de changement de partition est presque certainement meilleure. Si vous pouvez faire face aux longs temps de compilation et aux choix de plans originaux souvent vus avec les objets partitionnés - en particulier lorsque le nombre de partitions est important.

La combinaison de nombreuses fonctionnalités relativement nouvelles, en particulier près de leurs limites, est un excellent moyen d'obtenir de mauvais plans d'exécution. La profondeur de la prise en charge de l'optimiseur a tendance à s'améliorer avec le temps, mais l'utilisation de 15 000 partitions de magasin de colonnes signifie toujours que vous vivez à une époque intéressante.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.