Por que essas consultas semelhantes usam diferentes fases de otimização (processamento de transações x plano rápido)?

12

O código de exemplo neste item de conexão

Mostra um erro em que

SELECT COUNT(*)
FROM   dbo.my_splitter_1('2') L1
       INNER JOIN dbo.my_splitter_1('') L2
         ON L1.csv_item = L2.csv_item

Retorna os resultados corretos. Mas o seguinte retorna resultados incorretos (em 2014, usando o novo Estimador de cardinalidade)

SELECT
    (SELECT COUNT(*)
    FROM dbo.my_splitter_1('2') L1
     INNER JOIN dbo.my_splitter_1('') L2
        ON L1.csv_item = L2.csv_item)

Como ele carrega incorretamente os resultados para L2 em um spool de subexpressão comum, repete o resultado para o resultado L1.

Fiquei curioso para saber por que a diferença de comportamento entre as duas consultas. O sinalizador de rastreamento 8675 mostra que entra aquele que funciona search(0) - transaction processinge o que falha search(1) - quick plan.

Portanto, suponho que a disponibilidade de regras de transformação adicionais esteja por trás da diferença de comportamento (desabilitar o BuildGbApply ou GenGbApplySimple parece corrigi-lo, por exemplo).

Mas por que os dois planos para essas consultas muito semelhantes encontram diferentes fases de otimização? Pelo que li, search (0)requer pelo menos três tabelas e essa condição certamente não é atendida no primeiro exemplo.

Martin Smith
fonte

Respostas:

7

Cada estágio tem condições de entrada. "Ter pelo menos três referências de tabela" é uma das condições de entrada de que falamos ao fornecer exemplos simples, mas não é a única.

Geralmente, somente junções e uniões básicas são permitidas para entrada na pesquisa 0; subconsultas escalares, semi-junções etc. impedem a entrada para pesquisar 0. Esse estágio é realmente para as formas de consulta do tipo OLTP muito comuns. As regras necessárias para explorar as coisas menos comuns simplesmente não estão ativadas. Sua consulta de exemplo possui uma subconsulta escalar e, portanto, falha na entrada.

Também depende de como você conta as referências da tabela. Eu nunca examinei profundamente isso com funções, mas é possível que a lógica esteja contando as Funções com valor de tabela, bem como as variáveis ​​de tabela que elas produzem. Pode até estar contando a referência da tabela dentro da própria função - não tenho certeza; embora eu saiba que funções são apenas trabalho duro o tempo todo.

O erro com GenGbApplySimpleé feio. Essa forma do plano sempre foi uma possibilidade, mas foi rejeitada por razões de custo até que a alteração na cardinalidade da variável de tabela assumida para 100 linhas chegasse. É possível forçar a forma problemática do plano no CE anterior a 2014 com uma USE PLANdica, por exemplo.

Você está correto sobre o novo item do Connect ser o mesmo problema relatado anteriormente .

Para fornecer um exemplo, a seguinte consulta é qualificada para a pesquisa 0:

DECLARE @T AS table (c1 integer NULL);

SELECT U.c1, rn = ROW_NUMBER() OVER (ORDER BY U.c1) 
FROM 
(
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
) AS U;

Fazer uma pequena alteração para incluir uma subconsulta escalar significa que ela vai direto à pesquisa 1:

DECLARE @T AS table (c1 integer NULL);

SELECT U.c1, rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -- Changed!
FROM 
(
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
    UNION
    SELECT c1 FROM @T AS T
) AS U;
Paul White 9
fonte