Ao usar uma subconsulta para encontrar a contagem total de todos os registros anteriores com um campo correspondente, o desempenho é terrível em uma tabela com apenas 50 mil registros. Sem a subconsulta, a consulta é executada em alguns milissegundos. Com a subconsulta, o tempo de execução é superior a um minuto.
Para esta consulta, o resultado deve:
- Inclua apenas os registros dentro de um determinado intervalo de datas.
- Inclua uma contagem de todos os registros anteriores, sem incluir o registro atual, independentemente do intervalo de datas.
Esquema de Tabela Básico
Activity
======================
Id int Identifier
Address varchar(25)
ActionDate datetime2
Process varchar(50)
-- 7 other columns
Dados de exemplo
Id Address ActionDate (Time part excluded for simplicity)
===========================
99 000 2017-05-30
98 111 2017-05-30
97 000 2017-05-29
96 000 2017-05-28
95 111 2017-05-19
94 222 2017-05-30
resultados esperados
Para o intervalo de datas de 2017-05-29
a2017-05-30
Id Address ActionDate PriorCount
=========================================
99 000 2017-05-30 2 (3 total, 2 prior to ActionDate)
98 111 2017-05-30 1 (2 total, 1 prior to ActionDate)
94 222 2017-05-30 0 (1 total, 0 prior to ActionDate)
97 000 2017-05-29 1 (3 total, 1 prior to ActionDate)
Os registros 96 e 95 são excluídos do resultado, mas são incluídos na PriorCount
subconsulta
Consulta atual
select
*.a
, ( select count(*)
from Activity
where
Activity.Address = a.Address
and Activity.ActionDate < a.ActionDate
) as PriorCount
from Activity a
where a.ActionDate between '2017-05-29' and '2017-05-30'
order by a.ActionDate desc
Índice atual
CREATE NONCLUSTERED INDEX [IDX_my_nme] ON [dbo].[Activity]
(
[ActionDate] ASC
)
INCLUDE ([Address]) WITH (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
)
Pergunta
- Quais estratégias podem ser usadas para melhorar o desempenho dessa consulta?
Edit 1
Em resposta à pergunta do que posso modificar no banco de dados: posso modificar os índices, mas não a estrutura da tabela.
Edit 2
Agora adicionei um índice básico na Address
coluna, mas isso não pareceu melhorar muito. Atualmente, estou encontrando um desempenho muito melhor ao criar uma tabela temporária e inserir os valores sem o PriorCount
e atualizar cada linha com suas contagens específicas.
Editar 3
O Spool de Índice Joe Obbish (resposta aceita) encontrado foi o problema. Depois de adicionar um novo nonclustered index [xyz] on [Activity] (Address) include (ActionDate)
, os tempos de consulta caíram de mais de um minuto para menos de um segundo sem usar uma tabela temporária (consulte a edição 2).
Com a definição de índice que você tem para
IDX_my_nme
, o SQL Server poderá buscar usando aActionDate
coluna, mas não com aAddress
coluna. O índice contém todas as colunas necessárias para cobrir a subconsulta, mas provavelmente não é muito seletivo para essa subconsulta. Suponha que quase todos os dados na tabela tenham umActionDate
valor anterior a'2017-05-30'
. Uma busca deActionDate < '2017-05-30'
retornará quase todas as linhas do índice, que são filtradas ainda mais depois que a linha é buscada do índice. Se sua consulta retornar 200 linhas, você provavelmente faria quase 200 varreduras completas de índice emIDX_my_nme
, o que significa que você lerá cerca de 50.000 * 200 = 10 milhões de linhas do índice.É provável que a busca
Address
seja muito mais seletiva para sua subconsulta, embora você não tenha nos fornecido informações estatísticas completas sobre a consulta, então isso é uma suposição de minha parte. No entanto, suponha que você crie um índice em justAddress
e sua tabela tenha 10k valores exclusivos paraAddress
. Com o novo índice, o SQL Server precisará buscar apenas 5 linhas do índice para cada execução da subconsulta, então você lerá cerca de 200 * 5 = 1000 linhas do índice.Estou testando no SQL Server 2016, portanto, pode haver algumas pequenas diferenças de sintaxe. Abaixo estão alguns dados de exemplo nos quais fiz suposições semelhantes às acima para distribuição de dados:
Eu criei seu índice conforme descrito na pergunta. Estou testando essa consulta que retorna os mesmos dados da pergunta:
Eu recebo um carretel de índice. O que isso significa em um nível básico é que o otimizador de consulta cria um índice temporário dinamicamente porque nenhum dos índices existentes na tabela era adequado.
A consulta ainda termina rapidamente para mim. Talvez você não esteja obtendo a otimização do spool de índice em seu sistema ou há algo diferente na definição da tabela ou na consulta. Para fins educacionais, posso usar um recurso não documentado
OPTION (QUERYRULEOFF BuildSpool)
para desativar o carretel de índice. Veja como é o plano:Não se deixe enganar pela aparência de uma simples busca de índice. O SQL Server lê quase 10 milhões de linhas do índice:
Se eu for executar a consulta mais de uma vez, provavelmente não fará sentido para o otimizador de consulta criar um índice toda vez que for executado. Eu poderia criar um índice antecipado que seria mais seletivo para esta consulta:
O plano é semelhante ao anterior:
No entanto, com o novo índice, o SQL Server lê apenas 1.000 linhas do índice. 800 das linhas são retornadas para serem contadas. O índice pode ser definido para ser mais seletivo, mas isso pode ser bom o suficiente dependendo da sua distribuição de dados.
Se você não conseguir definir nenhum índice adicional na tabela, eu consideraria usar funções de janela. O seguinte parece funcionar:
Essa consulta faz uma única varredura dos dados, mas faz uma classificação cara e calcula a
ROW_NUMBER()
função para cada linha da tabela, então parece que há algum trabalho extra feito aqui:No entanto, se você realmente gosta desse padrão de código, pode definir um índice para torná-lo mais eficiente:
Isso move o tipo para o final, que será muito menos caro:
Se nada disso ajudar, você precisará adicionar mais informações à pergunta, de preferência incluindo planos de execução reais.