Por que alterar a ordem da coluna de junção declarada introduz uma classificação?

40

Eu tenho duas tabelas com colunas de chave identificadas, digitadas e indexadas. Um deles possui um índice clusterizado exclusivo , o outro possui um não exclusivo .

A configuração de teste

Script de instalação, incluindo algumas estatísticas realistas:

DROP TABLE IF EXISTS #left;
DROP TABLE IF EXISTS #right;

CREATE TABLE #left (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE UNIQUE CLUSTERED INDEX IX ON #left (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #left WITH ROWCOUNT=63800000, PAGECOUNT=186000;

CREATE TABLE #right (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE CLUSTERED INDEX IX ON #right (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #right WITH ROWCOUNT=55700000, PAGECOUNT=128000;

A reprodução

Quando ingresso nessas duas tabelas em suas chaves de cluster, espero uma associação MERGE de um para muitos, assim:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.a=r.a AND
    l.b=r.b AND
    l.c=r.c AND
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

Este é o plano de consulta que eu quero:

É isso que eu quero.

(Não importa os avisos, eles têm a ver com estatísticas falsas.)

No entanto, se eu alterar a ordem das colunas na junção, assim:

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.c=r.c AND     -- used to be third
    l.a=r.a AND     -- used to be first
    l.b=r.b AND     -- used to be second
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

... isto acontece:

O plano de consulta após alterar a ordem da coluna declarada na associação.

O operador Sort parece ordenar os fluxos de acordo com a ordem declarada da junção, ou seja c, a, b, d, e, f, g, h, o que adiciona uma operação de bloqueio ao meu plano de consulta.

Coisas que eu olhei

  • Eu tentei alterar as colunas para NOT NULL, mesmos resultados.
  • A tabela original foi criada com ANSI_PADDING OFF, mas criá-la com ANSI_PADDING ONnão afeta esse plano.
  • Eu tentei um em INNER JOINvez de LEFT JOIN, nenhuma mudança.
  • Eu o descobri em um SP2 Enterprise de 2014, criei uma reprodução em um desenvolvedor de 2017 (CU atual).
  • A remoção da cláusula WHERE na coluna principal do índice gera o bom plano, mas afeta os resultados .. :)

Finalmente, chegamos à questão

  • Isso é intencional?
  • Posso eliminar a classificação sem alterar a consulta (que é o código do fornecedor, então eu realmente prefiro não ...). Eu posso mudar a tabela e os índices.
Daniel Hutmacher
fonte

Respostas:

28

Isso é intencional?

É por design, sim. Infelizmente, a melhor fonte pública para essa afirmação foi perdida quando a Microsoft retirou o site de comentários do Connect, obliterando muitos comentários úteis dos desenvolvedores da equipe do SQL Server.

De qualquer forma, o atual design do otimizador não procura ativamente evitar tipos desnecessários por si só . Isso é encontrado com mais freqüência em funções de janelas e afins, mas também pode ser visto com outros operadores sensíveis a pedidos e, em particular, a pedidos preservados entre operadores.

No entanto, o otimizador é muito bom (em muitos casos) para evitar a classificação desnecessária, mas esse resultado normalmente ocorre por outros motivos além de tentar agressivamente diferentes combinações de pedidos. Nesse sentido, não se trata tanto de "espaço de pesquisa", mas de interações complexas entre os recursos do otimizador ortogonal que demonstraram aumentar a qualidade geral do plano a um custo aceitável.

Por exemplo, a classificação geralmente pode ser evitada simplesmente combinando um requisito de pedido (por exemplo, nível superior ORDER BY) a um índice existente. No seu caso, de maneira trivial, isso pode significar adicionar, ORDER BY l.a, l.b, l.c, l.d, l.e, l.f, l.g, l.h;mas isso é uma simplificação excessiva (e inaceitável porque você não deseja alterar a consulta).

De maneira mais geral, cada grupo de notas pode ser associado a propriedades necessárias ou desejadas, que podem incluir pedidos de entrada. Quando não há razão óbvia para impor uma ordem específica (por exemplo, para satisfazer um ORDER BY, ou para garantir resultados corretos de um operador físico sensível à ordem), há um elemento de 'sorte' envolvido. Escrevi mais sobre as especificidades disso, no que se refere à mesclagem de junção (no modo de união ou junção) em Evitando classificações com concatenação de junção de mesclagem . Muito disso vai além da área de superfície suportada do produto, portanto, trate-o como informativo e sujeito a alterações.

No seu caso particular, sim, você pode ajustar a indexação como jadarnel27 sugere para evitar as classificações; embora haja poucas razões para realmente preferir uma junção de mesclagem aqui. Você também pode sugerir uma opção entre a junção física de hash ou loop OPTION(HASH JOIN, LOOP JOIN)usando um Guia de Planejamento sem alterar a consulta, dependendo do seu conhecimento dos dados, e a troca entre o melhor, o pior e o desempenho de caso médio.

Por fim, como curiosidade, observe que os tipos podem ser evitados com uma simples ORDER BY l.b, ao custo de uma junção de muitos para muitos potencialmente menos eficiente b, sozinhos, com um residual complexo. Menciono isso principalmente como uma ilustração da interação entre os recursos do otimizador mencionados anteriormente e a maneira como os requisitos de nível superior podem se propagar.

Paul White diz que a GoFundMonica
fonte
19

Posso eliminar a classificação sem alterar a consulta (que é o código do fornecedor, então eu realmente prefiro não ...). Eu posso mudar a tabela e os índices.

Se você pode alterar os índices, alterar a ordem do índice #rightpara corresponder à ordem dos filtros na associação remove a classificação (para mim):

CREATE CLUSTERED INDEX IX ON #right (c, a, b, d, e, f, g, h)

Surpreendentemente (para mim, pelo menos), isso resulta em nenhuma consulta terminando com uma classificação.

Isso é intencional?

Observando a saída de alguns sinalizadores de rastreamento estranhos , há uma diferença interessante na estrutura final do Memo:

captura de tela da estrutura final da nota para cada consulta

Como você pode ver no "Grupo raiz" na parte superior, as duas consultas têm a opção de usar uma junção de mesclagem como a principal operação física para executar essa consulta.

Boa consulta

A junção sem a classificação é conduzida pelas opções 1 do grupo 29 e 1 do grupo 31 (cada uma das quais são varreduras de intervalo nos índices envolvidos). Ele é filtrado pelo grupo 27 (não mostrado), que é a série de operações de comparação lógica que filtram a junção.

Consulta incorreta

Aquele com a classificação é orientado pelas (novas) opções 3 que cada um desses dois grupos (29 e 31) possui. A opção 3 executa uma classificação física nos resultados das varreduras de intervalo mencionadas anteriormente (opção 1 de cada um desses grupos).

Por quê?

Por algum motivo, a opção de usar 29.1 e 31.1 diretamente como fontes para a junção de mesclagem nem sequer está disponível para o otimizador na segunda consulta. Caso contrário, acho que seria listado no grupo raiz entre as outras opções. Se estivesse disponível, ele definitivamente escolheria as operações de classificação massivamente mais caras.

Só posso concluir que:

  • este é um bug (ou mais provavelmente uma limitação) no algoritmo de pesquisa do otimizador
    • alterar os índices e as uniões para ter apenas 5 chaves remove a classificação da segunda consulta (6, 7 e 8 chaves têm a classificação).
    • Isso implica que o espaço de pesquisa com 8 teclas é tão grande que o otimizador simplesmente não tem tempo para identificar a solução sem classificação como uma opção viável antes de terminar cedo com o motivo "plano suficientemente bom encontrado"
    • parece um pouco problemático para mim que a ordem das condições de junção influencie muito o processo de busca do otimizador, mas na verdade isso está um pouco demais
  • a classificação é necessária para garantir a correção nos resultados
    • parece improvável, pois a consulta pode ser executada sem a classificação quando houver menos chaves ou as chaves forem especificadas em uma ordem diferente

Espero que alguém possa vir e explicar por que o tipo é necessário, mas achei que a diferença no prédio do Memorando era interessante o suficiente para postar como resposta.

Josh Darnell
fonte
11
Acredito que seu comentário sobre o espaço de pesquisa seja realmente o caso aqui. Para usar apenas os índices, o otimizador deve verificar se são suficientes para as condições. Após as 5 chaves, há muitas possibilidades de verificação antes que ele volte a ocorrer. Eu seria curioso, se todas as combinações de ordem da consulta foram enumerados, quantos o otimizador teria sucesso em vs volta queda
Mr.Mindor
E sim, a inconsistência parece um pouco complicada, mas provavelmente depende totalmente do algoritmo usado para verificar se os índices são suficientes. Se todas as combinações foram testadas, você provavelmente poderá ver o padrão nos resultados e determinar qual algoritmo é usado. Aposto que ele foi escrito para ter um desempenho ideal nos casos de uso mais típicos. Pode existir uma alternativa que seria capaz de encontrar a solução de 8 teclas de forma confiável dentro do prazo, mas ela é mais lenta que a solução atual quando há menos do que as chaves 3-4.
Mr.Mindor