Qual é o algoritmo por trás do operador EXCEPT?

10

Qual é o algoritmo interno de como o operador Except funciona nos bastidores do SQL Server? Será necessário internamente um hash de cada linha e comparar?

David Lozinksi realizou um estudo, SQL: A maneira mais rápida de inserir novos registros onde um ainda não existe. Ele mostrou que a instrução Except é a mais rápida para grandes linhas de número; intimamente ligada aos nossos resultados abaixo.

Suposição: Eu acho que a junção à esquerda seria mais rápida, pois compara apenas 1 coluna, exceto que levaria mais tempo, pois precisa comparar todas as colunas.
Com esses resultados, agora nosso pensamento é Exceto automática e internamente, um hash de cada linha? Eu olhei para o plano de execução Exceto e ele utiliza algum hash.

Histórico: Nossa equipe estava comparando duas tabelas de heap. Tabela A Linhas que não estão na Tabela B, foram inseridas na Tabela B.

As tabelas de heap (do sistema de arquivos de texto herdado) não possuem chaves / guias / identificadores primários. Algumas das tabelas tinham linhas duplicadas; portanto, encontramos o Hash de cada linha, removemos duplicatas e criamos identificadores de chave primária.

1) Primeiro, executamos uma instrução exceto, excluindo (coluna de hash)

select * from TableA
Except
Select * from TableB,

2) Em seguida, fizemos uma comparação de junção esquerda entre as duas tabelas no HashRowId

select * 
FROM dbo.TableA A
left join dbo.TableB B
    on A.RowHash =  B.RowHash
where B.Hash is null

surpreendentemente, o Except Statement Insert foi o mais rápido.

Na verdade, os resultados são mapeados perto dos resultados dos testes de David Lozinksi

insira a descrição da imagem aqui

Comunidade
fonte
11
Isso nem sempre será o caso. Encontrei resultados ligeiramente diferentes para leituras, por exemplo .
Aaron Bertrand

Respostas:

10

Qual é o algoritmo interno de como o operador Except funciona nos bastidores do SQL Server?

Eu não diria que existe um algoritmo interno especial para EXCEPT. Para A EXCEPT B, o mecanismo pega tuplas distintas (se necessário) de A e subtrai linhas que correspondem a B. Não há operadores de plano de consulta especiais. O distinto e a subtração são implementados através dos operadores típicos que você veria com uma classificação ou com uma junção. Junção de loop aninhada, junção de mesclagem e junção de hash são todas suportadas. Para mostrar isso, jogarei 15 milhões de linhas em um par de pilhas:

DROP TABLE IF EXISTS dbo.TABLE_1;

CREATE TABLE dbo.TABLE_1 (
    COL1 BIGINT NULL,
    COL2 BIGINT NULL
);

INSERT INTO dbo.TABLE_1 WITH (TABLOCK)
SELECT TOP (15000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), NULL
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);


DROP TABLE IF EXISTS dbo.TABLE_2;

CREATE TABLE dbo.TABLE_2 (
    COL1 BIGINT NULL,
    COL2 BIGINT NULL
);

INSERT INTO dbo.TABLE_2 WITH (TABLOCK)
SELECT TOP (15000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), NULL
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

O otimizador toma decisões habituais baseadas em custos sobre como implementar a classificação e a junção. Com dois montões, recebo uma junção de hash conforme o esperado. Você pode ver outros tipos de junção naturalmente adicionando índices ou alterando os dados em qualquer tabela. Abaixo forço a junção de mesclagem e loop com dicas apenas para fins ilustrativos:

junta-se

Será necessário internamente um hash de cada linha e comparar?

Não. Ele é implementado como qualquer outra junção. Uma diferença é que os NULLs são tratados como iguais. Este é um tipo especial de comparação que você pode ver no plano de execução: <Compare CompareOp="IS">. No entanto, você pode obter o mesmo plano com o T-SQL que não inclui a EXCEPTpalavra - chave. Por exemplo, o seguinte tem exatamente o mesmo plano de EXCEPTconsulta que a consulta que usa uma junção de hash:

SELECT t1.*
FROM
(
    SELECT DISTINCT COL1, COL2
    FROM dbo.TABLE_1
) t1
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.TABLE_2 t2
    WHERE (t1.COL1 = t2.COL1 OR (t1.COL1 IS NULL AND t2.COL1 IS NULL))
    AND (t1.COL2 = t2.COL2 OR (t1.COL2 IS NULL AND t2.COL2 IS NULL))
);

A difusão do XML dos planos de execução apenas revela diferenças superficiais em torno de aliases e coisas assim. Os resíduos da sonda para as junções de hash fazem a comparação de linhas. Eles são os mesmos para as duas consultas:

insira a descrição da imagem aqui

Se você ainda tiver dúvidas, executei o PerfView com a maior taxa de amostragem disponível para obter pilhas de chamadas para a consulta EXCEPTe a consulta sem ela. Aqui estão os resultados lado a lado:

insira a descrição da imagem aqui

Não há diferença real. As pilhas de chamadas nesse hash de referência estão presentes devido às correspondências de hash no plano. Se eu adicionar índices para obter uma junção de mesclagem natural, você não verá nenhuma referência ao hash nas pilhas de chamadas:

insira a descrição da imagem aqui

Qualquer hash que ocorre é devido à implementação de operadores de correspondência de hash. Não há nada de especial sobre o EXCEPTque leva a uma comparação interna de hash especial.

Joe Obbish
fonte