Por que a variável de tabela está forçando uma varredura de índice enquanto a tabela temporária usa a pesquisa e a pesquisa de favoritos?

18

Estou tentando entender por que o uso de uma variável de tabela está impedindo o otimizador de usar uma pesquisa de índice e, em seguida, a pesquisa de indicadores versus uma verificação de índice.

Preenchendo a tabela:

CREATE TABLE dbo.Test 
(
    RowKey INT NOT NULL PRIMARY KEY, 
    SecondColumn CHAR(1) NOT NULL DEFAULT 'x',
    ForeignKey INT NOT NULL 
) 

INSERT dbo.Test 
(
    RowKey, 
    ForeignKey
) 
SELECT TOP 1000000 
    ROW_NUMBER() OVER (ORDER BY (SELECT 0)),
    ABS(CHECKSUM(NEWID()) % 10)     
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2 

CREATE INDEX ix_Test_1 ON dbo.Test (ForeignKey) 

Preencha uma variável de tabela com um único registro e tente procurar a chave primária e a segunda coluna pesquisando na coluna de chave estrangeira:

DECLARE @Keys TABLE (RowKey INT NOT NULL) 

INSERT @Keys (RowKey) VALUES (10)

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey

Abaixo está o plano de execução:

insira a descrição da imagem aqui

Agora, a mesma consulta usando uma tabela temporária:

CREATE TABLE #Keys (RowKey INT NOT NULL) 

INSERT #Keys (RowKey) VALUES (10) 

SELECT 
    t.RowKey,
    t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    #Keys k
ON
    t.ForeignKey = k.RowKey

Este plano de consulta usa uma consulta de pesquisa e marcador:

insira a descrição da imagem aqui

Por que o otimizador está disposto a fazer a pesquisa de marcadores com a tabela temporária, mas não a variável da tabela?

A variável de tabela é usada neste exemplo para representar dados provenientes de um tipo de tabela definido pelo usuário em um procedimento armazenado.

Sei que a busca pelo índice pode não ser apropriada se o valor da chave estrangeira ocorrer centenas de milhares de vezes. Nesse caso, uma varredura provavelmente seria uma escolha melhor. Para o cenário que criei, não havia linha com o valor 10. Ainda acho o comportamento interessante e gostaria de saber se há uma razão para isso.

SQL Fiddle

A adição OPTION (RECOMPILE)não mudou o comportamento. O UDDT tem uma chave primária.

@@VERSION é o SQL Server 2008 R2 (SP2) - 10.50.4042.0 (X64) (Compilação 7601: Service Pack 1) (Hypervisor)

8kb
fonte

Respostas:

15

O motivo do comportamento é que o SQL Server não pode determinar quantas linhas corresponderão a ForeignKey, já que não há índice com RowKey como a coluna principal (pode deduzir isso das estatísticas na tabela #temp, mas elas não o fazem). existe para variáveis ​​de tabela / UDTTs), portanto, faz uma estimativa de 100.000 linhas, que é melhor tratada com uma varredura do que com uma busca + pesquisa. Quando o SQL Server percebe que há apenas uma linha, é tarde demais.

Você pode construir sua UDTT de maneira diferente; nas versões mais modernas do SQL Server, você pode criar índices secundários nas variáveis ​​da tabela, mas essa sintaxe não está disponível no 2008 R2.

BTW, você pode obter o comportamento de busca (pelo menos em meus testes limitados) se tentar evitar o bitmap / probe sugerindo uma associação de loops aninhados:

DECLARE @Keys TABLE (RowKey INT PRIMARY KEY); -- can't hurt

INSERT @Keys (RowKey) VALUES (10);

SELECT 
     t.RowKey
    ,t.SecondColumn
FROM
    dbo.Test t 
INNER JOIN 
    @Keys k
ON
    t.ForeignKey = k.RowKey
    OPTION (LOOP JOIN);

Eu aprendi esse truque com Paul White há vários anos. Obviamente, você deve ter cuidado ao colocar qualquer tipo de dica de junção no código de produção - isso pode falhar se as pessoas fizerem alterações nos objetos subjacentes e esse tipo específico de junção não for mais possível ou não for mais ideal.

Para consultas mais complexas, e quando você muda para o SQL Server 2012 ou superior, é possível que o sinalizador de rastreamento 2453 possa ajudar. Essa bandeira não ajudou com essa união simples, no entanto. E as mesmas isenções de responsabilidade se aplicariam - isso é apenas uma coisa alternativa que você geralmente não deve fazer sem uma tonelada de documentação e procedimentos rigorosos de teste de regressão.

Além disso, o Service Pack 1 está sem suporte, você deve entrar no Service Pack 3 + MS15-058 .

Aaron Bertrand
fonte
3

Variáveis ​​de tabela e tabelas temporárias são tratadas de maneira diferente de várias maneiras. Há uma ótima resposta aqui com muitos detalhes específicos sobre onde eles são diferentes.

Especificamente no seu caso, acho que o fato de que as tabelas temporárias podem ter estatísticas adicionais geradas e planos paralelos, enquanto as variáveis ​​da tabela têm estatísticas mais limitadas (sem estatísticas no nível da coluna) e nenhum plano paralelo é o culpado.

É muito melhor você despejar a variável da tabela em uma tabela temporária durante o procedimento armazenado.

Kenneth Fisher
fonte