Eu tenho uma consulta que atualmente está levando uma média de 2500ms para ser concluída. Minha mesa é muito estreita, mas existem 44 milhões de linhas. Que opções eu tenho para melhorar o desempenho ou isso é tão bom quanto ele ganha?
A pergunta
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
A mesa
CREATE TABLE [dbo].[Heartbeats](
[ID] [int] IDENTITY(1,1) NOT NULL,
[DeviceID] [int] NOT NULL,
[IsPUp] [bit] NOT NULL,
[IsWebUp] [bit] NOT NULL,
[IsPingUp] [bit] NOT NULL,
[DateEntered] [datetime] NOT NULL,
CONSTRAINT [PK_Heartbeats] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
O índice
CREATE NONCLUSTERED INDEX [CommonQueryIndex] ON [dbo].[Heartbeats]
(
[DateEntered] ASC,
[DeviceID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Adicionar índices adicionais ajudaria? Se sim, como eles seriam? O desempenho atual é aceitável, porque a consulta é executada apenas ocasionalmente, mas estou me perguntando como um exercício de aprendizado, há algo que eu possa fazer para tornar isso mais rápido?
ATUALIZAR
Quando altero a consulta para usar uma dica do índice de força, a consulta é executada em 50ms:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats] WITH(INDEX(CommonQueryIndex))
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'
A adição de uma cláusula DeviceID corretamente seletiva também atinge o intervalo de 50ms:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' AND DeviceID = 4;
Se eu adicionar ORDER BY [DateEntered], [DeviceID]
à consulta original, estou no intervalo de 50ms:
SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31'
ORDER BY [DateEntered], [DeviceID];
Todos eles usam o índice que eu esperava (CommonQueryIndex). Portanto, suponho que minha pergunta seja agora: existe uma maneira de forçar esse índice a ser usado em consultas como essa? Ou o tamanho da minha mesa está jogando muito fora do otimizador e devo apenas usar uma ORDER BY
ou uma dica?
Respostas:
Por que o otimizador não escolhe seu primeiro índice:
É uma questão de seletividade da coluna [DateEntered].
Você nos disse que sua tabela tem 44 milhões de linhas. o tamanho da linha é:
4 bytes, para o ID, 4 bytes para o ID do dispositivo, 8 bytes para a data e 1 byte para as colunas de 4 bits. isso significa 17 bytes + 7 bytes de sobrecarga para (tags, bitmap nulo, deslocamento de col var e contagem de col) totaliza 24 bytes por linha.
Isso seria traduzido para 140k páginas. Para armazenar esses 44 milhões de linhas.
Agora, o otimizador pode fazer duas coisas:
Agora, em um determinado momento, fica mais caro fazer todas essas pesquisas únicas no índice de cluster para cada entrada de índice encontrada no seu índice não em cluster. O limite para isso geralmente é a contagem total de pesquisas deve exceder 25% a 33% da contagem total de páginas da tabela.
Portanto, neste caso: 140k / 25% = 35000 linhas 140k / 33% = 46666 linhas.
(@RBarryYoung, 35k é 0,08% do total de linhas e 46666 é 0,10%, então acho que é aí que estava a confusão)
Portanto, se a sua cláusula where resultar em algo entre 35000 e 46666 linhas (isso está abaixo da cláusula superior!) É muito provável que o seu não clusterizado não seja usado e a varredura do índice clusterizado.
As únicas duas maneiras de mudar isso são:
Agora, certifique-se de criar um índice de cobertura mesmo quando usar um select *. Qualquer um que apenas crie uma sobrecarga enorme para suas inserções / atualizações / exclusões. Teríamos que saber mais sobre sua carga de trabalho (leitura versus gravação) para garantir que essa seja a melhor solução.
A mudança de datetime para smalldatetime é uma redução de 16% no tamanho no índice em cluster e uma redução de 24% no tamanho no índice não em cluster.
fonte
Existe um motivo específico para o seu PK estar em cluster? Muitas pessoas fazem isso porque o padrão é dessa maneira, ou acham que as PKs devem ser agrupadas. Não é assim. Os índices agrupados geralmente são melhores para consultas de intervalo (como esta) ou na chave estrangeira de uma tabela filha.
Um efeito de um índice de cluster é que ele agrupa todos os dados porque os dados são armazenados nos nós das folhas da árvore do cluster b. Portanto, supondo que você não esteja solicitando um intervalo 'muito amplo', o otimizador saberá exatamente qual parte da árvore b contém os dados e não precisará encontrar um identificador de linha e, em seguida, pular para onde os dados é (como acontece ao lidar com um índice NC). O que é "muito amplo" de um intervalo? Um exemplo ridículo seria solicitar 11 meses de dados de uma tabela que possui apenas um ano de registros. Obter um dia de dados não deve ser um problema, supondo que suas estatísticas estejam atualizadas. No entanto, o otimizador pode ter problemas se você estiver procurando os dados de ontem e não atualizar as estatísticas por três dias.
Como você está executando uma consulta "SELECT *", o mecanismo precisará retornar todas as colunas da tabela (mesmo se alguém adicionar uma nova que seu aplicativo não precisa naquele momento), para um índice de cobertura ou um índice com colunas incluídas não ajudará muito, se for o caso. (Se você estiver incluindo todas as colunas da tabela em um índice, estará fazendo algo errado.) O otimizador provavelmente ignorará esses índices NC.
Então o que fazer?
Minha sugestão seria descartar o índice NC, alterar a PK clusterizada para não clusterizada e criar um índice clusterizado em [DateEntered]. Mais simples é melhor, até que se prove o contrário.
fonte
Desde que você tenha esse "*" lá, a única coisa que eu poderia imaginar que faria muita diferença seria alterar sua definição de índice para isso:
Como observei nos comentários, ele deve usar esse índice, mas, se não, você pode persuadi-lo com uma ORDER BY ou uma dica de índice.
fonte
Eu consideraria isso um pouco diferente.
Eu despejaria a coluna datetime - alteraria para int. Tenha uma tabela de pesquisa ou faça uma conversão para a sua data.
Despejar o índice em cluster - deixe-o como um heap e crie um índice não em cluster na nova coluna INT que representa a data. ou seja, hoje seria 20121015. Essa ordem é importante. Dependendo da frequência com que você carrega a tabela, observe a criação desse índice na ordem DESC. O custo de manutenção será maior e você desejará introduzir um fator de preenchimento ou particionamento. O particionamento também ajudaria a diminuir o tempo de execução.
Por fim, se você pode usar o SQL 2012, tente usar SEQUENCE - ele superará a identidade () para inserções.
fonte