Crie um guia de plano para armazenar em cache o resultado CTE (spool lento)

19

Normalmente, crio guias de plano construindo primeiro uma consulta que usa o plano correto e copiando-a para uma consulta semelhante que não. No entanto, isso às vezes é complicado, principalmente se a consulta não for exatamente a mesma. Qual é a maneira correta de criar guias de plano a partir do zero?

O SQLKiwi mencionou a elaboração de planos no SSIS. Existe uma maneira ou uma ferramenta útil para ajudar na elaboração de um bom plano para o SQL Server?

A instância específica em questão é esta 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;

Existe QUALQUER maneira de fazer o resultado vir acima com exatamente 3 distintos guids e não mais? Espero poder responder melhor a perguntas no futuro, incluindo guias de plano com consultas do tipo CTE que são referenciadas várias vezes para superar algumas peculiaridades do SQL Server CTE.

孔夫子
fonte

Respostas:

14

Existe alguma maneira de fazer com que o resultado seja apresentado exatamente com 3 guias distintos e não mais? Espero poder responder melhor a perguntas no futuro, incluindo guias de plano com consultas do tipo CTE que são referenciadas várias vezes para superar algumas peculiaridades do SQL Server CTE.

Hoje nao. As expressões de tabela comum não recursivas (CTEs) são tratadas como definições de exibição em linha e expandidas na árvore de consultas lógicas em cada local em que são referenciadas (assim como as definições de exibição regulares) antes da otimização. A árvore lógica da sua consulta é:

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

Observe as duas âncoras de exibição e as seis chamadas para a função intrínseca newidantes que a otimização seja iniciada. No entanto, muitas pessoas consideram que o otimizador deve ser capaz de identificar que as subárvores expandidas eram originalmente um único objeto referenciado e simplificar de acordo. Também houve várias solicitações do Connect para permitir a materialização explícita de uma CTE ou tabela derivada.

Uma implementação mais geral faria com que o otimizador considerasse materializar expressões comuns arbitrárias para melhorar o desempenho ( CASEcom uma subconsulta é outro exemplo em que os problemas podem ocorrer hoje). A Microsoft Research publicou um documento (PDF) sobre isso em 2007, embora ainda não esteja implementado até o momento. Por enquanto, estamos limitados à materialização explícita usando coisas como variáveis ​​de tabela e tabelas temporárias.

O SQLKiwi mencionou a elaboração de planos no SSIS. Existe uma maneira ou uma ferramenta útil para ajudar na elaboração de um bom plano para o SQL Server?

Isso foi apenas uma ilusão da minha parte, e foi muito além da idéia de modificar os guias de plano. É possível, em princípio, escrever uma ferramenta para manipular o XML do plano de programa diretamente, mas sem a instrumentação específica do otimizador, o uso da ferramenta provavelmente seria uma experiência frustrante para o usuário (e o desenvolvedor passou a pensar nisso).

No contexto específico dessa questão, essa ferramenta ainda seria incapaz de materializar o conteúdo da CTE de uma maneira que pudesse ser usada por vários consumidores (para alimentar as duas entradas na junção cruzada nesse caso). O otimizador e o mecanismo de execução suportam spools de vários consumidores, mas apenas para fins específicos - nenhum dos quais poderia ser aplicado a esse exemplo em particular.

Embora não tenha certeza, tenho um palpite bastante forte de que os RelOps podem ser seguidos (Nested Loop, Lazy Spool), mesmo que a consulta não seja exatamente a mesma do plano - por exemplo, se você adicionou 4 e 5 ao CTE , ele ainda continua usando o mesmo plano (aparentemente testado no SQL Server 2012 RTM Express).

Há uma quantidade razoável de flexibilidade aqui. A ampla forma do plano XML é usada para orientar a busca de um plano final (embora muitos atributos sejam completamente ignorados, por exemplo, o tipo de particionamento nas trocas) e as regras normais de busca também sejam consideravelmente relaxadas. Por exemplo, a remoção antecipada de alternativas com base em considerações de custo é desativada, a introdução explícita de junções cruzadas é permitida e as operações escalares são ignoradas.

Há muitos detalhes para aprofundar, mas o posicionamento de Filtros e Escalares de computação não pode ser forçado, e os predicados do formulário column = valuesão generalizados para que um plano que contenha X = 1ou X = @Xpossa ser aplicado a uma consulta que contenha X = 502ou X = @Y. Essa flexibilidade específica pode ajudar bastante na busca de um plano natural para forçar.

No exemplo específico, a união constante de todos sempre pode ser implementada como uma verificação constante; o número de entradas para a União Tudo não importa.

Paul White diz que a GoFundMonica
fonte
3

Não há como (versões do SQL Server até 2012) reutilizar um único spool para as duas ocorrências do CTE. Detalhes podem ser encontrados na resposta do SQLKiwi.Mais abaixo, há duas maneiras de materializar o CTE duas vezes, o que é inevitável pela natureza da consulta. Ambas as opções resultam em uma contagem de guias líquida distinta de 6.

O link do comentário de Martin para o site de Quassnoi em um blog sobre o plano de guiar uma CTE foi uma inspiração parcial para essa pergunta. Ele descreve uma maneira de materializar uma CTE com a finalidade de uma subconsulta correlacionada, que é referenciada apenas uma vez, embora a correlação possa fazer com que ela seja avaliada várias vezes. Isso não se aplica à consulta na pergunta.

Opção 1 - Guia do Plano

Tomando dicas da resposta do SQLKiwi, reduzi o guia a um mínimo que ainda funcionará, por exemplo, os ConstantScannós listam apenas 2 operadores escalares que podem ser expandidos o suficiente para qualquer número.

;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>'
);

Opção 2 - Verificação remota

Aumentando as despesas da consulta e introduzindo uma verificação remota, o resultado é materializado.

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;
孔夫子
fonte
2

Com toda a seriedade, você não pode cortar os planos de execução xml do zero. Criá-los usando o SSIS é ficção científica. Sim, é tudo XML, mas eles são de universos diferentes. Olhando para o blog de Paul sobre esse tópico , ele está dizendo "da maneira que o SSIS permite ...", então você pode ter entendido errado? Não acho que ele esteja dizendo "use o SSIS para criar planos", mas sim "não seria ótimo poder criar planos usando uma interface de arrastar e soltar, como SSIS". Talvez, para uma consulta muito simples, você possa gerenciar isso, mas é uma extensão, possivelmente até uma perda de tempo. Trabalho ocupado, você pode dizer.

Se estou criando um plano para uma dica ou um guia de plano do USE PLAN, tenho algumas abordagens. Por exemplo, eu posso remover registros das tabelas (por exemplo, em uma cópia do banco de dados) para influenciar as estatísticas e incentivar o otimizador a tomar uma decisão diferente. Também usei variáveis ​​de tabela em vez de toda a tabela da consulta, para que o otimizador pense que toda tabela contém 1 registro. Em seguida, no plano gerado, substitua todas as variáveis ​​da tabela pelos nomes da tabela original e troque-a como o plano. Outra opção seria usar a opção WITH STATS_STREAM de UPDATE STATISTICS para falsificar estatísticas, que é o método usado ao clonar cópias de bancos de dados somente estatísticas.

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

Passei algum tempo mexendo nos planos de execução xml no passado e descobri que, no final, o SQL simplesmente diz "Não estou usando isso" e executa a consulta como deseja de qualquer maneira.

Para o seu exemplo específico, tenho certeza de que você sabe que pode usar o número de linhas 3 ou TOP 3 na consulta para obter esse resultado, mas acho que esse não é o seu ponto. A resposta correta seria realmente: use uma tabela temporária. Eu voto positivo:) Não seria uma resposta correta "gastar horas e até dias cortando seu próprio plano de execução XML personalizado, em que você tenta induzir o otimizador a fazer um spool preguiçoso para o CTE, que pode nem funcionar de qualquer maneira, seria inteligente. mas também seria impossível de manter ".

Não tentando ser construtivo lá, apenas minha opinião - espero que ajude.

wBob
fonte
Sério, os Planos XML são ignoráveis?!, Eu pensei que esse era o ponto. Se eles são inválidos, deve lançar.
22613 crokusek
Eu estava me referindo ao evento sem êxito do guia de plano.
wBob
2

Existe QUALQUER maneira ...

Finalmente, no SQL 2016 CTP 3.0, existe uma maneira, tipo de:)

Usando o sinalizador de rastreamento e Eventos Estendidos como detalhado por Dmitry Pilugin aqui , você pode (arbitrariamente) pescar três guids únicas dos estágios intermediários da execução da consulta.

NB Este código NÃO se destina à produção ou uso sério no que se refere à imposição do plano CTE, apenas um olhar alegre sobre um novo sinalizador de rastreamento e uma maneira diferente de fazer as coisas:

-- 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

Testado na versão (CTP3.2) - 13.0.900.73 (x64), apenas por diversão.

wBob
fonte
1

Descobri que o traceflag 8649 (forçar plano paralelo) induziu esse comportamento para a coluna guia da esquerda nas minhas instâncias de 2008, R2 e 2012. Não precisei usar o sinalizador no SQL 2005 onde o CTE se comportou corretamente. Tentei usar o plano gerado no SQL 2005 nas instâncias superiores, mas ele não foi validado.

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 )

Usando a dica, usando um guia de plano, incluindo a dica ou usando o plano gerado pela consulta com a dica ativada em um USE PLAN etc, tudo funcionou. cte newid

wBob
fonte
Obrigado por tentar novamente. A consulta não parece diferente com ou sem esse sinalizador de rastreamento em 2008/2012. Não tenho certeza se são minhas instâncias do SQL Server ou o que você está tentando mostrar. Eu ainda vejo 18 guias. O que você vê?
孔夫子
3 guias distintos no lado esquerdo (coluna guid), cada um repetindo três vezes. 9 guias únicos no lado direito (coluna guidb), então pelo menos o bit esquerdo está se comportando como você deseja. Adicionei uma imagem a outra resposta para esclarecer um pouco. Pequenos passos. Devo também observar no SQL 2005, recebo 6 guias exclusivos, 3 à esquerda e 3 à direita.
WBob 27/10/12
Também notei que remover o 'all' também recebe os 6 guias únicos, 3 de cada lado.
WBob 27/10/12
Pode fazer a traceflag não trabalho por ter maxdop servidor 1.
wBob