Eu tenho uma tabela com algumas dezenas de linhas. A configuração simplificada está seguindo
CREATE TABLE #data ([Id] int, [Status] int);
INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);
E eu tenho uma consulta que une essa tabela a um conjunto de linhas construídas com valor de tabela (feitas de variáveis e constantes), como
DECLARE @id1 int = 101, @id2 int = 105;
SELECT
COALESCE(p.[Code], 'X') AS [Code],
COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
(@id1, 'A'),
(@id2, 'B')
) p([Id], [Code])
FULL JOIN #data d ON d.[Id] = p.[Id];
O plano de execução da consulta está mostrando que a decisão do otimizador é usar a FULL LOOP JOIN
estratégia, o que parece apropriado, pois ambas as entradas possuem poucas linhas. Uma coisa que notei (e não posso concordar), porém, é que as linhas do TVC estão sendo colocadas em spool (veja a área do plano de execução na caixa vermelha).
Por que o otimizador introduz o spool aqui, qual é a razão para fazê-lo? Não há nada complexo além do carretel. Parece que não é necessário. Como se livrar dele neste caso, quais são as maneiras possíveis?
O plano acima foi obtido em
Microsoft SQL Server 2014 (SP2-CU11) (KB4077063) - 12.0.5579.0 (X64)
A coisa além do spool não é uma simples referência de tabela, que poderia simplesmente ser duplicada quando a alternativa de junção esquerda / anti semijunção é gerada.
Pode parecer um pouco com uma tabela (Constant Scan), mas para o otimizador* é uma
UNION ALL
das linhas separadas naVALUES
cláusula.A complexidade adicional é suficiente para o otimizador optar por fazer o spool e reproduzir as linhas de origem e não substituir o spool por um simples "obter tabela" posteriormente. Por exemplo, a transformação inicial da junção completa se parece com isso:
Observe os carretéis extras introduzidos pela transformação geral. Os carretéis acima de uma mesa simples são limpos posteriormente pela regra
SpoolGetToGet
.Se o otimizador tivesse uma
SpoolConstGetToConstGet
regra correspondente, ele poderia funcionar como você deseja, em princípio.Use uma tabela real (temporária ou variável) ou escreva a transformação da junção completa manualmente, por exemplo:
Plano para reescrita manual:
Este tem um custo estimado de 0,0067201 unidades, em comparação com 0,0203412 unidades do original.
* Pode ser observado como a
LogOp_UnionAll
na Árvore Convertida (TF 8605). Na Árvore de Entrada (TF 8606) é um arquivoLogOp_ConstTableGet
. A Árvore Convertida mostra a árvore de elementos de expressão do otimizador após análise, normalização, algebrização, associação e algum outro trabalho preparatório. A Árvore de entrada mostra os elementos após a conversão para a forma normal de negação (conversão NNF), colapso da constante de tempo de execução e alguns outros bits e bobs. O NNF convert inclui lógica para recolher uniões lógicas e tabelas comuns, entre outras coisas.O carretel de tabela está simplesmente criando uma tabela a partir dos dois conjuntos de tuplas presentes na
VALUES
cláusula.Você pode eliminar o spool inserindo esses valores em uma tabela temporária primeiro, assim:
Observando o plano de execução de sua consulta, vemos que a lista de saída contém duas colunas que usam o
Union
prefixo; esta é uma dica de que o spool está criando uma tabela de uma fonte de união:O
FULL OUTER JOIN
SQL Server requer que o SQL Server acesse os valoresp
duas vezes, uma para cada "lado" da junção. A criação de um spool permite que os loops internos resultantes se unam para acessar os dados em spool.Curiosamente, se você substituir o
FULL OUTER JOIN
por aLEFT JOIN
e aRIGHT JOIN
eUNION
os resultados juntos, o SQL Server não usará um spool.Observe que não estou sugerindo o uso da
UNION
consulta acima; para conjuntos maiores de entrada, pode não ser mais eficiente do que o simplesFULL OUTER JOIN
que você já tem.