Je crée normalement des guides de plan en construisant d'abord une requête qui utilise le plan correct et la copie sur la requête similaire qui ne le fait pas. Cependant, cela est parfois délicat, surtout si la requête n'est pas exactement la même. Quelle est la bonne façon de créer des guides de plan à partir de zéro?
SQLKIWI a mentionné l'établissement de plans en SSIS, existe-t-il un moyen ou un outil utile pour vous aider à établir un bon plan pour SQL Server?
L'instance spécifique en question est la CTE: sqlfiddle
with cte(guid,other) as (
select newid(),1 union all
select newid(),2 union all
select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other;
Est-ce [~ # ~ ~] n'importe où [~ # ~] de faire le résultat de faire exactement 3 distincts guid
s et non Suite? J'espère pouvoir mieux répondre à des questions à l'avenir en incluant des guides de plan avec des requêtes de type CTE qui sont référencées plusieurs fois pour surmonter certaines bizarreries SQL Server CTE.
Y a-t-il un moyen de faire le résultat de faire exactement 3 guidons distincts et plus encore? J'espère pouvoir mieux répondre aux questions à l'avenir en incluant des guides de plan avec des requêtes de type CTE qui sont référencées plusieurs fois pour surmonter certains Quiks SQL Server CTE.
Pas aujourd'hui. Les expressions de table communes non récursives (CTES) sont traitées comme des définitions de visualisation en ligne et élargies dans l'arborescence de la requête logique à chaque endroit où elles sont référencées (comme des définitions d'affichage régulières sont) avant l'optimisation. L'arbre logique de votre requête est:
LogOp_OrderByCOL: Union1007 ASC COL: Union1015 ASC
LogOp_Project COL: Union1006 COL: Union1007 COL: Union1014 COL: Union1015
LogOp_Join
LogOp_ViewAnchor
LogOp_UnionAll
LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
LogOp_ViewAnchor
LogOp_UnionAll
LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
LogOp_Project ScaOp_Intrinsic newid, ScaOp_Const
Remarquez les deux ancrages de vue et les six appels à la fonction intrinsèque newid
avant l'optimisation ne devient démarrer. Néanmoins, de nombreuses personnes considèrent que l'optimiseur devrait être capable d'identifier que les sous-arbres expansés étaient à l'origine un seul objet référencé et simplifient en conséquence. Il y a également eu plusieurs Connecter les demandes pour permettre une matérialisation explicite d'une table CTE ou dérivée.
Une mise en œuvre plus générale aurait que l'optimiseur envisagerait d'envisager de matérialiser des expressions communes arbitraires pour améliorer les performances (CASE
avec une sous-requête est un autre exemple sur lequel problèmes Peut se produire aujourd'hui). Microsoft Research Publié un document (PDF) sur le dos en 2007, bien qu'il ne reste pas malimenté à ce jour. Pour le moment, nous sommes limités à une matérialisation explicite à l'aide de choses comme des variables de table et des tables temporaires.
SQLKIWI a mentionné l'établissement de plans en SSIS, existe-t-il un moyen ou un outil utile pour vous aider à définir un bon plan pour SQL Server?
C'était juste - jolie pensée de ma part et s'est bien passé au-delà de l'idée de modifier les guides du plan. Il est en principe possible d'écrire un outil pour manipuler le plan de spectacle XML directement, mais sans instrumentation d'optimisation spécifique à l'aide de l'outil serait probablement une expérience frustrante de l'utilisateur (et le développeur vient en penser).
Dans le contexte particulier de cette question, un tel outil ne serait toujours pas en mesure de concrétiser le contenu de la CTE d'une manière qui pourrait être utilisé par plusieurs consommateurs (pour alimenter les deux entrées de la croix dans ce cas). L'optimiseur et le moteur d'exécution prennent en charge les bobines multi-consommateurs, mais uniquement à des fins spécifiques, dont aucun n'a pu être apporté pour s'appliquer à cet exemple particulier.
Bien que je ne sois pas certain, j'ai une housseuse assez forte que les liops peuvent être suivies (boucle imbriquée, bobine paresseuse) même si la requête n'est pas exactement la même que le plan - par exemple si vous avez ajouté 4 et 5 au CTE, il continue toujours d'utiliser le même plan (apparemment testé sur SQL Server 2012 RTM Express).
Il y a une quantité raisonnable de flexibilité ici. La forme large du plan XML est utilisée pour Guide la recherche d'un plan final (bien que de nombreux attributs soient ignorés complètement, par exemple, par exemple, le type de partitionnement sur les échanges) et les règles de recherche normales sont considérablement détendues. . Par exemple, la taille précoce des solutions de rechange basées sur des considérations de coûts est désactivée, l'introduction explicite de jointures croisées est autorisée et les opérations scalaires sont ignorées.
Il y a trop de détails pour entrer en profondeur, mais le placement de filtres et des scalaires de calcul ne peut pas être forcé, et les prédicats de la forme column = value
sont généralisés ainsi un plan contenant X = 1
ou X = @X
peut être appliqué à une requête contenant X = 502
ou X = @Y
. Cette flexibilité particulière peut aider grandement à trouver un plan naturel de force.
Dans l'exemple spécifique, l'Union constante peut toujours être mise en œuvre comme une analyse constante; Le nombre d'intrants de l'Union n'a pas d'importance.
Il n'y a aucun moyen (Versions SQL Server jusqu'à 2012) pour réutiliser une seule bobine pour les deux occurrences du CTE. Les détails peuvent être trouvés dans la réponse de SQLKIWI. Autres ci-dessous Voici deux façons de matérialiser le CTE deux fois, ce qui n'est inévitable pour la nature de la requête. Les deux options entraînent un nombre de GUID distinct net de 6.
Le lien du commentaire de Martin au site de Quassnoi sur un Blog sur le plan guidant un CTE était une inspiration partielle pour cette question. Il décrit un moyen de concrétiser un CTE à des fins d'une sous-requête corrélée, qui est référencée une seule fois que la corrélation peut le faire évaluer plusieurs fois. Cela ne s'applique pas à la requête dans la question.
En prenant des notes de la réponse de Sqlkiwi, j'ai parlé le guide sur un minimum nu qui fera toujours le travail, par exemple. Les nœuds ConstantScan
Listez uniquement 2 opérateurs scalaires qui peuvent suffisamment développer à n'importe quel nombre.
;with cte(guid,other) as (
select newid(),1 union all
select newid(),2 union all
select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other
OPTION(USE PLAN
N'<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.2" Build="11.0.2100.60" xmlns="http://schemas.Microsoft.com/sqlserver/2004/07/showplan">
<BatchSequence>
<Batch>
<Statements>
<StmtSimple StatementCompId="1" StatementEstRows="1600" StatementId="1" StatementOptmLevel="FULL" StatementOptmEarlyAbortReason="GoodEnoughPlanFound" StatementSubTreeCost="0.0444433" StatementText="with cte(guid,other) as (
 select newid(),1 union all
 select newid(),2 union all
 select newid(),3
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other;
" StatementType="SELECT" QueryHash="0x43D93EF17C8E55DD" QueryPlanHash="0xF8E3B336792D84" RetrievedFromCache="true">
<StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
<QueryPlan NonParallelPlanReason="EstimatedDOPIsOne" CachedPlanSize="96" CompileTime="13" CompileCPU="13" CompileMemory="1152">
<MemoryGrantInfo SerialRequiredMemory="0" SerialDesiredMemory="0" />
<OptimizerHardwareDependentProperties EstimatedAvailableMemoryGrant="157240" EstimatedPagesCached="1420" EstimatedAvailableDegreeOfParallelism="1" />
<RelOp AvgRowSize="47" EstimateCPU="0.006688" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1600" LogicalOp="Inner Join" NodeId="0" Parallel="false" PhysicalOp="Nested Loops" EstimatedTotalSubtreeCost="0.0444433">
<OutputList>
<ColumnReference Column="Union1163" />
</OutputList>
<Warnings NoJoinPredicate="true" />
<NestedLoops Optimized="false">
<RelOp AvgRowSize="27" EstimateCPU="0.000432115" EstimateIO="0.0112613" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Sort" NodeId="1" Parallel="false" PhysicalOp="Sort" EstimatedTotalSubtreeCost="0.0117335">
<OutputList>
<ColumnReference Column="Union1080" />
<ColumnReference Column="Union1081" />
</OutputList>
<MemoryFractions Input="0" Output="0" />
<Sort Distinct="false">
<OrderBy>
<OrderByColumn Ascending="true">
<ColumnReference Column="Union1081" />
</OrderByColumn>
</OrderBy>
<RelOp AvgRowSize="27" EstimateCPU="4.0157E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Constant Scan" NodeId="2" Parallel="false" PhysicalOp="Constant Scan" EstimatedTotalSubtreeCost="4.0157E-05">
<OutputList>
<ColumnReference Column="Union1080" />
<ColumnReference Column="Union1081" />
</OutputList>
<ConstantScan>
<Values>
<Row>
<ScalarOperator ScalarString="newid()">
<Intrinsic FunctionName="newid" />
</ScalarOperator>
<ScalarOperator ScalarString="(1)">
<Const ConstValue="(1)" />
</ScalarOperator>
</Row>
<Row>
<ScalarOperator ScalarString="newid()">
<Intrinsic FunctionName="newid" />
</ScalarOperator>
<ScalarOperator ScalarString="(2)">
<Const ConstValue="(2)" />
</ScalarOperator>
</Row>
</Values>
</ConstantScan>
</RelOp>
</Sort>
</RelOp>
<RelOp AvgRowSize="27" EstimateCPU="0.0001074" EstimateIO="0.01" EstimateRebinds="0" EstimateRewinds="39" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Lazy Spool" NodeId="83" Parallel="false" PhysicalOp="Table Spool" EstimatedTotalSubtreeCost="0.0260217">
<OutputList>
<ColumnReference Column="Union1162" />
<ColumnReference Column="Union1163" />
</OutputList>
<Spool>
<RelOp AvgRowSize="27" EstimateCPU="0.000432115" EstimateIO="0.0112613" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Sort" NodeId="84" Parallel="false" PhysicalOp="Sort" EstimatedTotalSubtreeCost="0.0117335">
<OutputList>
<ColumnReference Column="Union1162" />
<ColumnReference Column="Union1163" />
</OutputList>
<MemoryFractions Input="0" Output="0" />
<Sort Distinct="false">
<OrderBy>
<OrderByColumn Ascending="true">
<ColumnReference Column="Union1163" />
</OrderByColumn>
</OrderBy>
<RelOp AvgRowSize="27" EstimateCPU="4.0157E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="40" LogicalOp="Constant Scan" NodeId="85" Parallel="false" PhysicalOp="Constant Scan" EstimatedTotalSubtreeCost="4.0157E-05">
<OutputList>
<ColumnReference Column="Union1162" />
<ColumnReference Column="Union1163" />
</OutputList>
<ConstantScan>
<Values>
<Row>
<ScalarOperator ScalarString="newid()">
<Intrinsic FunctionName="newid" />
</ScalarOperator>
<ScalarOperator ScalarString="(1)">
<Const ConstValue="(1)" />
</ScalarOperator>
</Row>
<Row>
<ScalarOperator ScalarString="newid()">
<Intrinsic FunctionName="newid" />
</ScalarOperator>
<ScalarOperator ScalarString="(2)">
<Const ConstValue="(2)" />
</ScalarOperator>
</Row>
</Values>
</ConstantScan>
</RelOp>
</Sort>
</RelOp>
</Spool>
</RelOp>
</NestedLoops>
</RelOp>
</QueryPlan>
</StmtSimple>
</Statements>
</Batch>
</BatchSequence>
</ShowPlanXML>'
);
En augmentant les dépenses de la requête et en introduisant une analyse à distance, le résultat est matérialisé.
with cte(guid,other) as (
select *
from OPENQUERY([TESTSQL\V2012], '
select newid(),1 union all
select newid(),2 union all
select newid(),3') x)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other;
En tout grave, vous ne pouvez pas réduire les plans d'exécution XML à partir de zéro. Les créer avec SSI sont la science-fiction. Oui, tout est tout XML, mais ils viennent de différents univers. En regardant le blog de Paul sur ce sujet , il dise "beaucoup dans la manière dont la SSIS permet ..." donc éventuellement que vous avez mal compris? Je ne pense pas qu'il disait "Utilisez SSIS pour créer des plans" mais plutôt "ne serait-il pas bon de pouvoir créer des plans à l'aide d'une interface de glisser-déposer comme SSIS ". Peut-être que pour une requête très simple, vous pourriez simplement gérer cela, mais c'est un étirement, éventuellement même une perte de temps. Travail occupé que vous pourriez dire.
Si je crée un plan pour un guide d'indice ou de plan de plan d'utilisation, j'ai quelques approches. Par exemple, je pourrais supprimer des enregistrements des tables (par exemple sur une copie de la DB) pour influencer les statistiques et encourager l'optimiseur à prendre une décision différente. J'ai également utilisé des variables de table au lieu de tout le tableau de la requête afin que l'optimiseur pense que chaque table contient 1 enregistrement. Ensuite, dans le plan généré, remplacez toutes les variables de table avec les noms de table d'origine et l'échange dans le plan en tant que plan. Une autre option serait d'utiliser l'option STATS_STREAM des statistiques de mise à jour des statistiques de Spoof, qui est la méthode utilisée lors de la clonage des statistiques, des copies uniquement des bases de données, par exemple
UPDATE STATISTICS
[dbo].[yourTable]([PK_yourTable])
WITH
STATS_STREAM = 0x0100etc,
ROWCOUNT = 10000,
PAGECOUNT = 93
J'ai passé du temps à bricoler avec des plans d'exécution XML dans le passé et j'ai constaté que, à la fin, SQL va juste "je n'utilise pas ça" et dirige la requête comment elle veut quand même.
Pour votre exemple spécifique, je suis sûr que vous êtes conscient que vous pouvez utiliser ensemble RowCount 3 ou Top 3 dans la requête pour obtenir ce résultat, mais je suppose que ce n'est pas votre point. Le correct La réponse serait vraiment: utilisez une table Temp. Je parvenir à upvote que:) non une réponse correcte serait "Passablement des heures les jours des jours de réduction de votre propre plan d'exécution XML personnalisé où vous essayez d'asperger l'optimeur de faire une bobine paresseuse pour le CTE qui pourrait même ne pas fonctionner de toute façon, regarderait intelligent mais serait également impossible de maintenir ".
Ne pas essayer d'être inconstructeur là-bas, juste mon opinion - espère que cela aide.
J'ai trouvé Traceflag 8649 (Plan parallèle Force) induisait ce comportement pour la colonne GUID de gauche sur mes instances de 2008, R2 et 2012. Je n'avais pas besoin d'utiliser le drapeau sur SQL 2005 où la CTE s'est comportée correctement. J'ai essayé d'utiliser le plan généré dans SQL 2005 dans les instances plus élevées, mais cela ne validerait pas.
with cte(guid,other) as (
select newid(),1 union all
select newid(),2 union all
select newid(),3)
select a.guid, a.other, b.guid guidb, b.other otherb
from cte a
cross join cte b
order by a.other, b.other
option ( querytraceon 8649 )
Utilisation de l'indice, à l'aide d'un guide de plan, y compris l'indice ou en utilisant le plan généré par la requête avec le soupçon sur un plan d'utilisation, etc. Tous fonctionnaient.