Por que essa consulta não está usando meu índice não clusterizado e como posso fazer isso?

12

Como acompanhamento desta pergunta sobre o aumento do desempenho da consulta, gostaria de saber se existe uma maneira de usar meu índice por padrão.

Esta consulta é executada em cerca de 2,5 segundos:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Este é executado em cerca de 33ms:

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

Há um índice em cluster no campo [ID] (pk) e um índice não em cluster em [DateEntered], [DeviceID]. A primeira consulta usa o índice em cluster, a segunda consulta usa meu índice não em cluster. Minha pergunta é de duas partes:

  • Por que, como as duas consultas possuem uma cláusula WHERE no campo [DateEntered], o servidor usa o índice de cluster no primeiro, mas não no segundo?
  • Como posso fazer com que o índice não agrupado seja usado por padrão nesta consulta, mesmo sem o orderby? (Ou por que eu não iria querer esse comportamento?)
Nate
fonte
DateEntered é um DateTime; nesse caso, estou usando a parte da data, mas às vezes eu consulto a data e a hora juntas.
Nate

Respostas:

9

a primeira consulta faz uma varredura de tabela com base no limite que expliquei anteriormente: É possível aumentar o desempenho da consulta em uma tabela estreita com milhões de linhas?

(provavelmente sua consulta sem a TOP 1000cláusula retornará mais de 46k linhas. ou em algum lugar entre 35k e 46k. (a área cinza ;-))

a segunda consulta, deve ser ordenada. Como o índice NC é ordenado na ordem que você deseja, é mais barato para o otimizador usá-lo e, em seguida, para as pesquisas de indicadores no índice clusterizado para obter as colunas ausentes comparadas com a realização de uma varredura de índice clusterizado e a necessidade de para pedir isso.

inverta a ordem das colunas na ORDER BYcláusula e você retornará a uma varredura de índice em cluster, pois o NC INDEX é inútil.

editar esqueceu a resposta para sua segunda pergunta, por que você não quer isso

O uso de um índice sem cobertura não agrupado em cluster significa que um ID da linha é pesquisado no índice NC e, em seguida, as colunas ausentes precisam ser pesquisadas no índice agrupado (o índice agrupado contém todas as colunas de uma tabela). As E / S para pesquisar as colunas ausentes no índice clusterizado são E / S aleatórias.

A chave aqui é ALEATÓRIA. porque para cada linha encontrada no índice NC, os métodos de acesso precisam procurar uma nova página no índice clusterizado. Isso é aleatório e, portanto, muito caro.

Agora, por outro lado, o otimizador também pode fazer uma varredura de índice em cluster. Ele pode usar os mapas de alocação para procurar intervalos de varredura e apenas começar a ler o índice em cluster em grandes partes. Isso é seqüencial e muito mais barato. (contanto que sua tabela não esteja fragmentada :-)) A desvantagem é que o índice cluster INTEIRO precisa ser lido. Isso é ruim para o seu buffer e potencialmente uma enorme quantidade de pedidos de veiculação. mas ainda, E / S sequenciais.

No seu caso, o otimizador decide algo entre 35k e 46k linhas, é mais barato para uma varredura completa de índice em cluster. Sim, está errado. E em muitos casos, com índices estreitos e sem cluster, sem WHEREcláusulas seletivas ou uma tabela grande, isso dá errado. (Sua mesa é pior, porque também é uma mesa muito estreita.)

Agora, adicionar o ORDER BYtorna mais caro verificar o índice clusterizado completo e depois solicitar os resultados. Em vez disso, o otimizador assume que é mais barato usar o índice NC já solicitado e, em seguida, paga a penalidade aleatória de IO pelas pesquisas de favoritos.

Portanto, seu pedido é um tipo de solução "dica de consulta" perfeita. MAS, em um determinado momento, quando os resultados da sua consulta forem tão grandes, a penalidade para as entradas / saídas aleatórias da pesquisa de favoritos será tão grande que se tornará mais lenta. Presumo que o otimizador altere os planos novamente para a verificação de índice em cluster antes desse ponto, mas você nunca sabe ao certo.

No seu caso, desde que suas inserções sejam ordenadas por data inserida, conforme discutido no bate-papo e na pergunta anterior (consulte o link), é melhor criar o índice de cluster na coluna DataDefinida.

Edward Dortland
fonte
20

Expressar a consulta usando sintaxe diferente às vezes pode ajudar a comunicar seu desejo de usar um índice não em cluster para o otimizador. Você deve encontrar o formulário abaixo para fornecer o plano que deseja:

SELECT
    [ID],
    [DeviceID],
    [IsPUp],
    [IsWebUp],
    [IsPingUp],
    [DateEntered]
FROM [dbo].[Heartbeats]
WHERE
    [ID] IN
(
    -- Keys
    SELECT TOP (1000)
        [ID]
    FROM [dbo].[Heartbeats]
    WHERE 
        [DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
        AND [DateEntered]  < CONVERT(datetime, '2011-08-31', 121)
);

Plano de consulta

Compare esse plano com o produzido quando o índice não agrupado é forçado com uma dica:

SELECT TOP (1000) 
    * 
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE 
    [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

Plano de dica de índice forçado

Os planos são essencialmente os mesmos (uma pesquisa de chave nada mais é do que uma pesquisa no índice de cluster). Os dois formulários de plano executam apenas uma consulta no índice não agrupado em cluster e no máximo 1000 pesquisas no índice agrupado.

A diferença importante está na posição do operador Top. Posicionado entre as duas buscas, o Top impede que o otimizador substitua as duas operações de busca por uma varredura logicamente equivalente do índice em cluster. O otimizador trabalha substituindo partes de um plano lógico por operações relacionais equivalentes. A parte superior não é um operador relacional; portanto, a reescrita impede a transformação em uma varredura de índice em cluster. Se o otimizador fosse capaz de reposicionar o operador Top, ainda assim preferiria a varredura ao invés da pesquisa +, devido à maneira como a estimativa de custos funciona.

Custeio de varreduras e buscas

Em um nível muito alto, o modelo de custo do otimizador para varreduras e buscas é bastante simples: estima que 320 buscas aleatórias custam o mesmo que ler 1350 páginas em uma varredura. Isso provavelmente tem pouca semelhança com os recursos de hardware de qualquer sistema de E / S moderno em particular, mas funciona razoavelmente bem como um modelo prático.

O modelo também faz uma série de suposições simplificadoras, sendo uma delas a suposição de que toda consulta é iniciada sem dados ou páginas de índice já no cache. A implicação é que toda E / S resultará em uma E / S física - embora isso raramente seja o caso na prática. Mesmo com um cache frio, a pré-busca e a leitura antecipada significam que as páginas necessárias provavelmente estão na memória quando o processador de consultas precisa delas.

Outra consideração é que a primeira solicitação de uma linha que não está na memória fará com que a página inteira seja buscada no disco. Os pedidos subsequentes de linhas na mesma página provavelmente não terão uma E / S física. O modelo de custeio contém lógica para levar em conta efeitos como esse, mas não é perfeito.

Todas essas coisas (e mais) significa que o otimizador tende a mudar para uma verificação mais cedo do que provavelmente deveria. A E / S aleatória é apenas 'muito mais cara' do que a E / S 'seqüencial' se uma operação física resultar - o acesso a páginas na memória é realmente muito rápido. Mesmo quando uma leitura física é necessária, uma varredura pode não resultar em leituras sequenciais devido à fragmentação e as buscas podem ser colocadas de modo que o padrão seja essencialmente seqüencial. Acrescente a isso a característica de desempenho variável dos sistemas de E / S modernos (especialmente de estado sólido) e tudo começa a parecer muito instável.

Metas de linha

A presença de um operador Top em um plano modifica a abordagem de custo. O otimizador é inteligente o suficiente para saber que encontrar 1000 linhas usando uma varredura provavelmente não exigirá a varredura de todo o índice em cluster - ele pode parar assim que 1000 linhas forem encontradas. Ele define uma 'meta de linha' de 1000 linhas no operador Top e usa informações estatísticas para trabalhar a partir daí para estimar quantas linhas ele espera precisar da origem da linha (uma varredura nesse caso). Eu escrevi sobre os detalhes deste cálculo aqui .

As imagens nesta resposta foram criadas usando o SQL Sentry Plan Explorer .

Paul White 9
fonte