Creare una guida di piano per il risultato CTE della cache (spool pigro)


19

Normalmente creo guide di piano costruendo prima una query che utilizza il piano corretto e copiandola nella query simile che non lo fa. Tuttavia, a volte è complicato, soprattutto se la query non è esattamente la stessa. Qual è il modo giusto di creare guide di piano da zero?

SQLKiwi ha menzionato l'elaborazione di piani in SSIS, esiste un modo o uno strumento utile per fornire un buon piano per SQL Server?

L'istanza specifica in questione è questa 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;

Esiste UN MODO per ottenere esattamente 3 risultati distinti guide non più? Spero di poter rispondere meglio alle domande in futuro includendo guide di piano con query di tipo CTE a cui si fa riferimento più volte per superare alcune stranezze CTE di SQL Server.


Risposte:


14

Esiste UN MODO per ottenere il risultato esattamente con 3 guide distinte e non di più? Spero di poter rispondere meglio alle domande in futuro includendo guide di piano con query di tipo CTE a cui si fa riferimento più volte per superare alcune stranezze CTE di SQL Server.

Non oggi. Le espressioni di tabella comune (CTE) non ricorsive vengono trattate come definizioni di vista in linea ed espanse nella struttura della query logica in ogni posizione a cui vengono referenziate (proprio come le definizioni di vista normali) prima dell'ottimizzazione. L'albero logico per la tua query è:

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

Notare le due ancore di visualizzazione e le sei chiamate alla funzione intrinseca newidprima di iniziare l'ottimizzazione. Tuttavia, molte persone ritengono che l'ottimizzatore dovrebbe essere in grado di identificare che i sottoalberi espansi erano originariamente un singolo oggetto referenziato e semplificare di conseguenza. Sono state inoltre presentate diverse richieste Connect per consentire la materializzazione esplicita di una tabella CTE o derivata.

Un'implementazione più generale consentirebbe all'ottimizzatore di prendere in considerazione la materializzazione di espressioni comuni arbitrarie per migliorare le prestazioni ( CASEcon una subquery è un altro esempio in cui oggi possono verificarsi problemi ). Microsoft Research ha pubblicato un documento (PDF) su questo nel 2007, anche se fino ad oggi non è stato implementato. Per il momento, siamo limitati alla materializzazione esplicita usando cose come variabili di tabella e tabelle temporanee.

SQLKiwi ha menzionato l'elaborazione di piani in SSIS, esiste un modo o uno strumento utile per fornire un buon piano per SQL Server?

Questo era solo un pio desiderio da parte mia, e andò ben oltre l'idea di modificare le guide dei piani. È possibile, in linea di principio, scrivere uno strumento per manipolare direttamente l'XML del piano di spettacolo, ma senza una strumentazione di ottimizzazione specifica l'utilizzo dello strumento sarebbe probabilmente un'esperienza frustrante per l'utente (e lo sviluppatore arriva a pensarci).

Nel particolare contesto di questa domanda, tale strumento non sarebbe ancora in grado di materializzare i contenuti CTE in un modo che potrebbe essere utilizzato da più consumatori (per alimentare entrambi gli input nel cross join in questo caso). L'ottimizzatore e il motore di esecuzione supportano spool multi-consumatore, ma solo per scopi specifici, nessuno dei quali potrebbe essere fatto applicare a questo esempio particolare.

Anche se non sono sicuro, ho la sensazione abbastanza forte che RelOps possa essere seguito (Nested Loop, Lazy Spool) anche se la query non è esattamente la stessa del piano, ad esempio se hai aggiunto 4 e 5 al CTE , continua ancora a utilizzare lo stesso piano (apparentemente - testato su SQL Server 2012 RTM Express).

C'è una ragionevole quantità di flessibilità qui. L'ampia forma del piano XML viene utilizzata per guidare la ricerca di un piano finale (sebbene molti attributi siano completamente ignorati, ad esempio il tipo di partizionamento negli scambi) e anche le normali regole di ricerca siano notevolmente allentate. Ad esempio, la potatura anticipata di alternative basata su considerazioni di costo è disabilitata, è consentita l'introduzione esplicita di join incrociati e le operazioni scalari vengono ignorate.

Esistono troppi dettagli da approfondire, ma il posizionamento di filtri e scalari di calcolo non può essere forzato e i predicati del modulo column = valuesono generalizzati, quindi un piano contenente X = 1o X = @Xpuò essere applicato a una query contenente X = 502o X = @Y. Questa particolare flessibilità può essere di grande aiuto nel trovare un piano naturale per forzare.

Nell'esempio specifico, Union All costante può sempre essere implementato come scansione costante; il numero di input nell'Unione Tutto non ha importanza.


3

Non esiste alcun modo (versioni di SQL Server fino al 2012) di riutilizzare un singolo spool per entrambe le occorrenze del CTE. I dettagli sono disponibili nella risposta di SQL Kiwi. Di seguito sono riportati due modi per materializzare due volte il CTE, il che è inevitabile per la natura della query. Entrambe le opzioni danno come risultato un numero netto netto di 6.

Il collegamento dal commento di Martin al sito di Quassnoi su un blog sul piano di guida di un CTE è stata fonte di parziale ispirazione per questa domanda. Descrive un modo per materializzare un CTE ai fini di una sottoquery correlata, a cui si fa riferimento solo una volta, sebbene la correlazione possa far sì che venga valutata più volte. Ciò non si applica alla query nella domanda.

Opzione 1 - Guida al piano

Prendendo spunti dalla risposta di SQL Kiwi, ho ridotto la guida a un minimo indispensabile che continuerà a fare il lavoro, ad esempio i ConstantScannodi elencano solo 2 operatori scalari che possono espandersi sufficientemente a qualsiasi numero.

;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 (&#xD;&#xA;  select newid(),1 union all&#xD;&#xA;  select newid(),2 union all&#xD;&#xA;  select newid(),3&#xD;&#xA;select a.guid, a.other, b.guid guidb, b.other otherb&#xD;&#xA;from cte a&#xD;&#xA;cross join cte b&#xD;&#xA;order by a.other, b.other;&#xD;&#xA;" 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>'
);

Opzione 2 - Scansione remota

Aumentando le spese della query e introducendo una scansione remota, il risultato si materializza.

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;

2

In tutta serietà non è possibile tagliare da zero i piani di esecuzione XML. La loro creazione tramite SSIS è fantascienza. Sì, è tutto XML, ma provengono da universi diversi. Guardando il blog di Paul su questo argomento , sta dicendo "molto nel modo in cui SSIS lo permette ..." quindi forse hai frainteso? Non penso che stia dicendo "usa SSIS per creare piani", ma piuttosto "non sarebbe bello poter creare piani usando un'interfaccia drag and drop come SSIS". Forse, per una query molto semplice, potresti quasi gestirlo, ma è un allungamento, forse anche una perdita di tempo. Lavoro intenso che potresti dire.

Se sto creando un piano per un suggerimento o una guida di piano USE PLAN, ho un paio di approcci. Ad esempio, potrei rimuovere i record dalle tabelle (ad esempio su una copia del db) per influenzare le statistiche e incoraggiare l'ottimizzatore a prendere una decisione diversa. Ho anche usato le variabili di tabella anziché tutta la tabella nella query, quindi l'ottimizzatore pensa che ogni tabella contenga 1 record. Quindi, nel piano generato, sostituisci tutte le variabili di tabella con i nomi di tabella originali e scambialo come piano. Un'altra opzione sarebbe quella di utilizzare l'opzione WITH STATS_STREAM di UPDATE STATISTICS per falsificare le statistiche che è il metodo utilizzato quando si clonano copie solo statistiche di database

UPDATE STATISTICS 
    [dbo].[yourTable]([PK_yourTable]) 
WITH 
    STATS_STREAM = 0x0100etc, 
    ROWCOUNT = 10000, 
    PAGECOUNT = 93

Ho passato un po 'di tempo a armeggiare con i piani di esecuzione XML in passato e ho scoperto che alla fine, SQL va semplicemente "Non lo sto usando" ed esegue la query come vuole comunque.

Per il tuo esempio specifico, sono sicuro che sei consapevole di poter usare set rowcount 3 o TOP 3 nella query per ottenere quel risultato, ma immagino che non sia questo il punto. La risposta corretta sarebbe davvero: usa una tabella temporanea. Vorrei che:) Non una risposta corretta sarebbe "passare ore o giorni a tagliare il proprio piano di esecuzione XML personalizzato in cui si tenta di indurre l'ottimizzatore a fare una bobina pigra per il CTE che potrebbe anche non funzionare comunque, sembrerebbe intelligente ma sarebbe anche impossibile da mantenere ".

Non cercare di essere non costruttivo lì, solo la mia opinione - spero che aiuti.


Scherzi a parte, i piani XML sono ignorabili?!, Pensavo che fosse questo il punto? Se non sono validi, dovrebbe essere lanciato.
crokusek,

Mi riferivo all'evento non riuscito della guida del piano.
mercoledì

2

C'è UN MODO ...

Finalmente in SQL 2016 CTP 3.0 c'è un modo, tipo di:)

Utilizzando il flag di traccia e gli Eventi estesi come dettagliato da Dmitry Pilugin qui , è possibile (in qualche modo arbitrariamente) estrarre tre guide uniche dalle fasi intermedie dell'esecuzione della query.

NB Questo codice NON è destinato alla produzione o all'utilizzo serio per quanto riguarda la forzatura del piano CTE, semplicemente uno sguardo spensierato su una nuova bandiera traccia e un modo diverso di fare le cose:

-- Configure the XEvents session; with ring buffer target so we can collect it
CREATE EVENT SESSION [query_trace_column_values] ON SERVER 
ADD EVENT sqlserver.query_trace_column_values
ADD TARGET package0.ring_buffer( SET max_memory = 2048 )
WITH ( MAX_MEMORY = 4096 KB, EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY = 30 SECONDS, MAX_EVENT_SIZE = 0 KB, MEMORY_PARTITION_MODE = NONE, TRACK_CAUSALITY = OFF , STARTUP_STATE = OFF )
GO

-- Start the session
ALTER EVENT SESSION [query_trace_column_values] ON SERVER
STATE = START;
GO

-- Run the query, including traceflag
DBCC TRACEON(2486);
SET STATISTICS XML ON;
GO

-- Original query
;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 ( recompile )
go

SET STATISTICS XML OFF;
DBCC TRACEOFF(2486);
GO

DECLARE @target_data XML

SELECT @target_data = CAST( target_data AS XML )
FROM sys.dm_xe_sessions AS s 
    INNER JOIN sys.dm_xe_session_targets AS t ON t.event_session_address = s.address
WHERE s.name = 'query_trace_column_values'


--SELECT @target_data td

-- Arbitrarily fish out 3 unique guids from intermediate stage of the query as collected by XEvent session
;WITH cte AS
(
SELECT
    n.c.value('(data[@name = "row_id"]/value/text())[1]', 'int') row_id,
    n.c.value('(data[@name = "column_value"]/value/text())[1]', 'char(36)') [guid]
FROM @target_data.nodes('//event[data[@name="column_id"]/value[. = 1]][data[@name="row_number"]/value[. < 4]][data[@name="node_name"]/value[. = "Nested Loops"]]') n(c)
)
SELECT *
FROM cte a
    CROSS JOIN cte b
GO

-- Stop the session
ALTER EVENT SESSION [query_trace_column_values] ON SERVER
STATE = STOP;
GO

-- Drop the session
IF EXISTS ( select * from sys.server_event_sessions where name = 'query_trace_column_values' )
DROP EVENT SESSION [query_trace_column_values] ON SERVER 
GO

Testato sulla versione (CTP3.2) - 13.0.900.73 (x64), solo per divertimento.


1

Ho trovato traceflag 8649 (forza piano parallelo) indotto questo comportamento per la colonna guida a sinistra sulle mie istanze 2008, R2 e 2012. Non avevo bisogno di usare il flag su SQL 2005 in cui il CTE si comportava correttamente. Ho provato a utilizzare il piano generato in SQL 2005 nelle istanze superiori ma non è stato convalidato.

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 )

O usando il suggerimento, usando una guida di piano che includa il suggerimento o usando il piano generato dalla query con il suggerimento attivo in un PIANO DI UTILIZZO ecc. Tutto ha funzionato. cte newid


Grazie per riprovare. La query non ha un aspetto diverso con o senza quel flag di traccia su 2008/2012. Non sono sicuro che si tratti delle mie istanze di SQL Server o di ciò che stai cercando di mostrare. Vedo ancora 18 guide. Cosa vedi?
孔夫子

3 guide distinte sul lato sinistro (colonna guid), ciascuna ripetendo tre volte. 9 guide uniche sul lato destro (colonna guida), quindi almeno il bit sinistro si sta comportando come vuoi lol. Ho aggiunto un'immagine ad un'altra risposta per chiarire, si spera, un po '. Piccoli passi. Dovrei anche notare in SQL 2005, ottengo 6 guide uniche, 3 a sinistra, 3 a destra.
wBob,

Ho anche notato che la rimozione del "tutto" consente di ottenere anche le 6 guide uniche, 3 per parte.
wBob,

Può far sì che il traceflag non funzioni avendo server maxdop 1.
wBob,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.