Procurar predicado que não usa todas as colunas disponíveis

8

Eu tenho um problema estranho de compilação de consultas que é difícil de reproduzir. Isso acontece apenas com alta carga e não pode ser facilmente repetido.

  • Existe uma tabela T com as colunas A, B, C, D.
  • Há um índice clusterizado não exclusivo em T (A, B, C, D).
  • Há uma consulta SELECT * FROM T ONDE A = @ P1 AND B = @ P2 AND (C = @ P3 OU C = @ P4) AND D = @ P5. A condição de busca está em todas as colunas do índice em cluster, a terceira coluna possui um OR.

O problema é que o plano de consulta dessa consulta possui o Predicado de busca apenas em A e B! O predicado em C e D é um predicado comum, portanto, isso significa que a árvore de pesquisa nas colunas C e D não é utilizada.

Os tipos de dados para todos os parâmetros correspondem aos tipos de dados da coluna.

Alguém poderia dar alguma dica de por que isso poderia estar acontecendo? A versão do SQL é 2008 R2 (SP1) - 10.50.2789.0 (X64)


fonte
Você já conseguiu um plano para uma consulta parametrizada que realiza uma busca de igualdade nas 4 colunas? Se sim, você está usando OPTION (RECOMPILE)?
Martin Smith

Respostas:

8

Para uma consulta parametrizada, ele não pode apenas fazer duas buscas em

WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5 

e

WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5 

Porque se @P3 = @P4isso traria incorretamente linhas duplicadas. Portanto, seria necessário um operador que removeu duplicatas delas primeiro.

A partir de um teste rápido, esse aspecto parece depender do tamanho da tabela, independentemente de você ter ou não isso. No teste abaixo 245/ 246linhas é o ponto de corte entre os planos (esse também foi o ponto de corte entre o índice que cabe tudo em uma página e ele se torna 2 páginas de folha e uma página raiz).

CREATE TABLE T(A INT,B INT,C INT,D INT)

INSERT INTO T
SELECT TOP (245) 1,2,3,5
FROM master..spt_values v1

CREATE CLUSTERED INDEX IX ON T(A, B, C, D)

SELECT index_level,page_count, record_count
FROM sys.dm_db_index_physical_stats(db_id(),object_id('T'),1,NULL, 'DETAILED')

DECLARE @C1 INT = 3,
        @C2 INT = 4

 SELECT * FROM T WHERE A=1 AND B=2 AND (C=@C1 OR C=@C2) AND D=5

 DROP TABLE T

1 páginas / 245 linhas

Esse plano tem uma busca A=1 AND B=2com um predicado residual em(C=@C1 OR C=@C2) AND D=5

Plano 1

2 folhas Páginas / 246 linhas

Plano 2

No segundo plano, os operadores extras são responsáveis ​​por remover quaisquer duplicatas do @C1,@C2primeiro antes de realizar as buscas.

A busca no segundo plano é na verdade uma busca de intervalo entre A=1 AND B=2 AND C > Expr1010e A=1 AND B=2 AND C < Expr1011com um predicado residual ativado D=5. Ainda não é uma busca de igualdade nas quatro colunas. Mais informações sobre os operadores de plano adicionais podem ser encontradas aqui .

A adição OPTION (RECOMPILE)permite inspecionar os valores dos parâmetros em busca de duplicatas no tempo de compilação e produz um plano com duas buscas de igualdade.

Você também pode conseguir isso com

;WITH CTE
     AS (SELECT DISTINCT ( C )
         FROM   (VALUES (@C1),
                        (@C2)) V(C))
SELECT CA.*
FROM   CTE
       CROSS APPLY (SELECT *
                    FROM   T
                    WHERE A=1 AND B=2 AND D=5  AND C = CTE.C) CA

Plano 3

Mas, na verdade, nesse caso de teste, seria contraproducente, pois duas buscas no índice de página única, em vez de uma, aumentam a IO lógica.

Martin Smith
fonte
11
Fiz alguns testes nesta questão ontem à noite antes de você adicionar seu primeiro comentário. Cheguei a ver o comportamento, mas não entendi o que estava causando (@ P3 = @ P4), então +1 para isso (já o fiz). Eu acho que um select .. union select ...também daria a você duas buscas, além da etapa adicional de remover duplicatas do resultado.
Mikael Eriksson
11
@MikaelEriksson - Mas SELECT * FROM T WHERE A=1 AND B=2 AND C=@C1 AND D=5 UNION SELECT * FROM T WHERE A=1 AND B=2 AND C=@C2 AND D=5pode remover incorretamente duplicatas que devem ser retornadas. No meu exemplo de dados onde eu preguiçosamente povoada todos com o mesmo valor que ele iria retornar 1 linha não256
Martin Smith
2
Ah sim. E o OP até especificou que a chave não é exclusiva. Lucke me eu não tentei responder a este :).
Mikael Eriksson
4

Concordo totalmente com a análise de Martin. Esta consulta não pode produzir uma pesquisa nas quatro colunas devido ao predicado OU, a menos que (talvez) com OPTION (RECOMPILE). Suponho que essa seja uma consulta com agulha no palheiro, e provavelmente você não deseja uma sobrecarga extra.

Que tal agora:

IF @P3=@P4
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
ELSE
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
    UNION ALL
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5.

Eu não testei isso, mas a outra parte deve fornecer 2 pesquisas em todos os 4 valores e uma união barata por meio de concatenação. No caso em que ambas as buscas acabem na mesma página, não acho que uma leitura lógica extra da página traga muito tempo. No entanto, dependendo dos seus dados, as duas buscas podem muito bem estar em páginas diferentes.

Vlad G.
fonte