O Plano de Execução NÃO está usando INDEX, ele usa Digitalização de Tabela

9

Eu sei que quando se trata de usar um índice ou uma verificação de tabela, o SQL Server usa estatísticas para ver qual é o melhor.

Eu tenho uma mesa com 20 milhões de linhas. Eu tenho um índice em (SnapshotKey, Measure) e esta consulta:

select Measure, SnapshotKey, MeasureBand
from t1
where Measure = 'FinanceFICOScore'
group by Measure, SnapshotKey, MeasureBand

A consulta retorna 500k linhas. Portanto, a consulta seleciona apenas 2,5% das linhas da tabela.

A questão é por que o SQL Server não usa o índice não clusterizado que eu tenho e, em vez disso, usa uma verificação de tabela?

As estatísticas são atualizadas.

É bom mencionar que o desempenho da consulta é bom.

Digitalização de mesa

Digitalização de mesa

Índice Forçado

Índice de força

Estrutura da tabela / índice

CREATE TABLE [t1](
    [SnapshotKey] [int] NOT NULL,
    [SnapshotDt] [date] NOT NULL,
    [Measure] [nvarchar](30) NOT NULL,
    [MeasureBand] [nvarchar](30) NOT NULL,
    -- and many more fields
) ON [PRIMARY]

Não há PK na tabela, pois é um data warehouse.

CREATE NONCLUSTERED INDEX [nci_SnapshotKeyMeasure] ON [t1]
(
    [SnapshotKey] ASC,
    [Measure] ASC
)

fonte

Respostas:

16

A busca de índice pode não ser a melhor opção se você retornar muitas linhas e / ou as linhas forem muito amplas. As pesquisas podem ser caras se o seu índice não estiver cobrindo. Veja o item 2 aqui .

No seu cenário, o otimizador de consultas estima que a realização de 50.000 pesquisas individuais será mais cara que uma única varredura. A escolha do otimizador entre varredura e procura (com pesquisas de RID para as colunas necessárias para a consulta, mas não presentes no índice não clusterizado) é baseada no custo estimado de cada alternativa.

O otimizador sempre escolhe a alternativa de menor custo que considera. Se você observar a propriedade Custo estimado da subárvore no nó raiz dos dois planos de execução, verá que o plano de varredura tem um custo estimado mais baixo que o plano de busca. Como resultado, o otimizador escolheu a verificação. Essa é essencialmente a resposta para sua pergunta.

Agora, o modelo de custo usado pelo otimizador é baseado em suposições e "números mágicos" que dificilmente correspondem às características de desempenho do sistema. Em particular, uma suposição feita no modelo é que a consulta começa a ser executada com nenhum dos dados ou páginas de índice necessários já na memória. Outra é que a E / S sequencial (esperada para uma varredura) é mais barata que o padrão de E / S aleatório assumido para pesquisas RID. Existem muitas outras suposições e advertências, muitas para detalhar aqui.

No entanto, o modelo de custo como um todo demonstrou produzir geralmente planos "suficientemente bons" para a maioria das consultas, na maioria dos esquemas de bancos de dados, na maioria das configurações de hardware, na maioria das vezes, em qualquer lugar. Isso é uma conquista e tanto, se você pensar bem.

As limitações do modelo e outros fatores às vezes significam que o otimizador escolhe um plano que não é, de fato, "suficientemente bom". Você relata que "o desempenho é bom", de modo que não parece ser o caso aqui.

Aaron Bertrand
fonte
9

Na verdade, você possui 595.947 linhas correspondentes, o que representa cerca de 3% dos seus dados. Portanto, o custo da pesquisa aumenta rapidamente. Suponha que você tenha 100 linhas por página em sua tabela, ou seja, 200.000 páginas para ler em uma varredura de tabela. Isso é muito mais barato do que fazer 595.947 pesquisas.

Com a GROUP BYcláusula da pergunta, acho que você estará melhor com uma chave composta ativada (Measure, SnapshotKey, MeasureBand).

Veja a sugestão "índice ausente". Ele diz para você incluir colunas para evitar as pesquisas. De maneira mais geral, se você fizer referência a outras colunas na sua consulta, elas precisarão estar nas chaves ou na INCLUDEcláusula do novo índice. Caso contrário, ainda será necessário fazer as 595.947 pesquisas para obter esses valores.

Por exemplo, para a consulta:

select Measure, SnapshotKey, MeasureBand, SUM(NumLoans), SUM(PrinBal)
from t1
where Measure = 'FinanceFICOScore'
group by Measure, SnapshotKey, MeasureBand

... você precisaria de:

CREATE INDEX ixWhatever 
ON t1 (Measure, SnapshotKey, MeasureBand) 
INCLUDE (NumLoans,PrinBal);
Rob Farley
fonte
6
  1. O campo na sua condição WHERE não é o campo principal do índice.

  2. Você measuredefinida como NVARCHAR assim prefixar o literal com um N: where Measure = N'FinanceFICOScore'.

Considere criar um índice clusterizado em SnapshotKey. Se for exclusivo, pode ser um PK (e agrupado). Se não for exclusivo, não poderá ser um PK, mas ainda pode ser um Índice de Cluster não exclusivo. Em seguida, seu índice não agrupado estaria apenas na measurecoluna.

E, considerando que o primeiro campo no GROUP BYé também measure, isso também se beneficiaria por measureser o campo principal.

De fato, para esta operação, talvez seja necessário definir o Índice Não Clusterizado em Measure, SnapshotKey, MeasureBand, nessa ordem exata, pois corresponde à GROUP BYcláusula. Em termos de tamanho, isso só é realmente adicionado, MeasureBandpois o índice NonClustered já é baseado Measuree MeasureKeyjá está incluído no índice, pois agora é a chave do Índice Clusterizado (não, Measurenão será duplicado no índice NonClustered).

O @Rob mencionou em um comentário excluído em sua resposta que resolver esse problema exige apenas que o Índice Não Clusterizado seja definido com esses três campos nessa ordem e que a criação de um Índice Clusterizado (não exclusivo) SnapshotKeynão seja necessária . Embora ele esteja provavelmente correto (eu esperava que menos campos funcionassem), eu ainda argumentaria que ter o Índice de Cluster é benéfico não apenas para esta operação, mas provavelmente para a maioria das outras.

Solomon Rutzky
fonte
A discussão sobre esta resposta foi movida para o bate-papo .
Paul White 9