Como o SQL Server sabe que os predicados estão correlacionados?

15

Ao diagnosticar consultas do SQL Server 2008 R2 com baixa estimativa de cardinalidade (apesar da indexação simples, estatísticas atualizadas etc.) e, portanto, com planos de consultas ruins, encontrei um artigo da KB talvez relacionado: CORREÇÃO: Desempenho ruim ao executar uma consulta que contém predicados AND correlacionados no SQL Server 2008 ou no SQL Server 2008 R2 ou no SQL Server 2012

Eu posso adivinhar o que o artigo da KB quer dizer com "correlacionado", por exemplo, o predicado nº 2 e o predicado nº 1 têm como alvo as mesmas linhas.

Mas não sei como o SQL Server sabe sobre essas correlações. Uma tabela precisa de um índice de várias colunas contendo colunas de ambos os predicados? O SQL usa estatísticas para verificar se os valores de uma coluna estão correlacionados com outra? Ou algum outro método é usado?

Estou perguntando isso por duas razões:

  1. para determinar quais das minhas tabelas e consultas podem ser aprimoradas usando esse hotfix
  2. para saber o que devo fazer na indexação, estatística, etc., para afetar o número 1
Justin Grant
fonte

Respostas:

20

Considere o plano simples de consulta e execução AdventureWorks mostrado abaixo. A consulta contém predicados conectados AND. A estimativa de cardinalidade do otimizador é 41.211 linhas:

-- Estimate 41,211 rows
SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionID BETWEEN 100000 AND 168336
    AND TH.TransactionDate BETWEEN '2007-09-01' AND '2008-03-13';

Plano de execução padrão

Usando estatísticas padrão

Dadas apenas estatísticas de coluna única, o otimizador produz essa estimativa estimando a cardinalidade de cada predicado separadamente e multiplicando as seletividades resultantes juntas. Essa heurística assume que os predicados são completamente independentes.

Dividir a consulta em duas partes facilita a visualização do cálculo:

-- Estimate 68,336.4 rows
SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionID BETWEEN 100000 AND 168336;

A tabela Histórico de transações contém 113.443 linhas no total, portanto, a estimativa 68.336,4 representa uma seletividade de 68336,4 / 113443 = 0,60238533 para esse predicado. Essa estimativa é obtida usando as informações do histograma da TransactionIDcoluna e os valores constantes especificados na consulta.

-- Estimate 68,413 rows
SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionDate BETWEEN '2007-09-01' AND '2008-03-13';

Esse predicado tem uma seletividade estimada de 68413.0 / 113443 = 0.60306056 . Novamente, é calculado a partir dos valores constantes do predicado e do histograma do TransactionDateobjeto estatístico.

Supondo que os predicados sejam completamente independentes, podemos estimar a seletividade dos dois predicados juntos, multiplicando-os. A estimativa final da cardinalidade é obtida multiplicando a seletividade resultante pelas 113.443 linhas na tabela base:

0,60238533 * 0,60306056 * 113443 = 41210,987

Após o arredondamento, esta é a estimativa 41.211 vista na consulta original (o otimizador também usa matemática de ponto flutuante internamente).

Não é uma ótima estimativa

As colunas TransactionIDe TransactionDatetêm uma estreita correlação no conjunto de dados do AdventureWorks (como geralmente aumentam monotonicamente chaves e colunas de data). Essa correlação significa que a suposição de independência é violada. Como conseqüência, o plano de consulta pós-execução mostra 68.095 linhas em vez dos 41.211 estimados:

Plano pós-execução

Bandeira de rastreamento 4137

A ativação desse sinalizador de rastreamento altera as heurísticas usadas para combinar predicados. Em vez de assumir total independência, o otimizador considera que as seletividades dos dois predicados são próximas o suficiente para que possam ser correlacionadas:

-- Estimate 68,336.4
SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionID BETWEEN 100000 AND 168336
    AND TH.TransactionDate BETWEEN '2007-09-01' AND '2008-03-13'
OPTION (QUERYTRACEON 4137);

Lembre-se de que o TransactionIDpredicado sozinho estimou 68.336,4 linhas e o TransactionDatepredicado sozinho estimou 68.413 linhas. O otimizador escolheu a menor dessas duas estimativas em vez de multiplicar as seletividades.

Essa é apenas uma heurística diferente, é claro, mas que pode ajudar a melhorar as estimativas de consultas com ANDpredicados correlacionados . Cada predicado é considerado para uma possível correlação, e há outros ajustes feitos quando muitas ANDcláusulas estão envolvidas, mas esse exemplo serve para mostrar o básico.

Estatísticas de várias colunas

Isso pode ajudar nas consultas com correlações, mas as informações do histograma ainda são baseadas apenas na coluna principal das estatísticas. As seguintes estatísticas de várias colunas candidatas diferem, portanto, de uma maneira importante:

CREATE STATISTICS
    [stats Production.TransactionHistory TransactionID TransactionDate]
ON Production.TransactionHistory
    (TransactionID, TransactionDate);

CREATE STATISTICS
    [stats Production.TransactionHistory TransactionDate TransactionID]
ON Production.TransactionHistory
    (TransactionDate, TransactionID);

Tomando apenas um deles, podemos ver que a única informação extra são os níveis extras da densidade 'all'. O histograma ainda contém apenas informações detalhadas sobre a TransactionDatecoluna.

DBCC SHOW_STATISTICS
    (
        'Production.TransactionHistory', 
        'stats Production.TransactionHistory TransactionDate TransactionID'
    );

Estatísticas de várias colunas

Com essas estatísticas de várias colunas no lugar ...

SELECT COUNT_BIG(*)
FROM Production.TransactionHistory AS TH
WHERE 
    TH.TransactionID BETWEEN 100000 AND 168336
    AND TH.TransactionDate BETWEEN '2007-09-01' AND '2008-03-13';

... o plano de execução mostra uma estimativa exatamente igual a quando apenas as estatísticas de coluna única estavam disponíveis:

Plano de estatísticas de várias colunas

Paul White restabelece Monica
fonte