Simulei dados de teste que reproduzem principalmente o seu problema:
INSERT INTO [dbo].[TestTable] WITH (TABLOCK)
SELECT TOP (7000000) N'*NOT GDPR*', N'*NOT GDPR*', N'*NOT GDPR*', 0, DATEADD(DAY, q.RN / 16965, '20160801')
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
) q
ORDER BY q.RN
OPTION (MAXDOP 1);
DROP INDEX IF EXISTS [dbo].[TestTable].IX_TestTable_Date;
CREATE NONCLUSTERED INDEX IX_TestTable_Date ON [dbo].[TestTable] ([Date]);
Estatísticas para a consulta que usa o índice não clusterizado:
Tabela 'TestTable'. Contagem de varredura 1, leituras lógicas 1299838, leituras físicas 0, leituras antecipadas 0, leituras lógicas lob 0, leituras físicas lob 0, leituras físicas lob 0, leituras antecipadas lob.
Tempos de execução do SQL Server: tempo de CPU = 984 ms, tempo decorrido = 988 ms.
Estatísticas para a consulta que usa o índice em cluster:
Tabela 'TestTable'. Contagem de varredura 1, leituras lógicas 72609, leituras físicas 0, leituras antecipadas 0, leituras lógicas lob 0, leituras físicas lob 0, leituras físicas lob 0, leituras antecipadas lob.
Tempos de execução do SQL Server: tempo de CPU = 781 ms, tempo decorrido = 772 ms.
Chegando à sua pergunta:
É possível aproveitar esse fato para melhorar o desempenho da minha consulta?
Sim. Você pode usar o índice não clusterizado que você já precisa para encontrar com eficiência o id
valor máximo que precisa ser atualizado. Se você salvar isso em uma variável e filtrar, obterá um plano de consulta para a atualização que faz a verificação do índice em cluster (sem a classificação) que para mais cedo e, portanto, faz menos IO. Aqui está uma implementação:
DECLARE @Id INT;
SELECT TOP (1) @Id = Id
FROM dbo.TestTable
WHERE [Date] <= '25 August 2016'
ORDER BY [Date] DESC, Id DESC;
UPDATE TestTable
SET TestCol='*GDPR*', TestCol2='*GDPR*', TestCol3='*GDPR*', Anonymised=1
WHERE [Id] < @Id AND [Date] <= '25 August 2016'
AND [Anonymised] <> 1 -- optional
OPTION (MAXDOP 1);
Execute estatísticas para a nova consulta:
Tabela 'TestTable'. Contagem de varreduras 1, leituras lógicas 3, leituras físicas 0, leituras antecipadas 0, leituras lógicas lob 0, leituras lógicas lob 0, leituras físicas lob 0, leituras antecipadas lob.
Tabela 'TestTable'. Contagem de varredura 1, leituras lógicas 4776, leituras físicas 0, leituras de pré-leitura 0, leituras lógicas de lob 0, leituras físicas de lob 0, leituras físicas de lob 0, leituras de leitura antecipada de lob 0.
Tempos de execução do SQL Server: tempo de CPU = 515 ms, tempo decorrido = 510 ms.
Bem como o plano de consulta:
Com tudo isso dito, seu desejo de acelerar a consulta sugere que você planeja executar a consulta mais de uma vez. No momento, sua consulta tem um filtro aberto na date
coluna. É realmente necessário anonimizar as linhas mais de uma vez? Você pode evitar atualizar ou varrer linhas que já foram anonimizadas? Certamente deve ser mais rápido atualizar um intervalo de datas com datas dos dois lados. Você também pode adicionar a Anonymised
coluna ao seu índice, mas esse índice precisará ser atualizado durante sua UPDATE
consulta. Em resumo, evite processar os mesmos dados repetidamente, se puder.
A consulta original que você possui com a classificação é mais lenta devido ao trabalho realizado no Clustered Index Update
operador. A quantidade de tempo gasto no índice procura e a classificação é de apenas 407 ms. Você pode ver isso no plano real. O plano é executado no modo de linha, para que o tempo gasto na classificação seja o tempo desse operador junto com todos os operadores filhos:
Isso deixa o operador de classificação com cerca de 1600 ms de tempo. O SQL Server precisa ler páginas do índice clusterizado para executar a atualização. Você pode ver que o Clustered Index Update
operador faz 1205921 leituras lógicas. Você pode ler mais sobre a classificação de otimizações para DML e a pré-busca otimizada nesta postagem de blog de Paul White .
O outro plano de consulta que você possui (sem a classificação) leva 683 ms para a verificação de índice em cluster e cerca de 550 ms para o Clustered Index Update
operador. O operador de atualização não faz nenhum pedido de entrada / saída para esta consulta.
A resposta simples para o motivo pelo qual o plano com a classificação é mais lento é que o SQL Server faz leituras mais lógicas no índice de cluster para esse plano em comparação com o plano de verificação de índice em cluster. Mesmo se todos os dados necessários estiverem na memória, ainda haverá um custo adicional para fazer essas leituras lógicas. É muito mais difícil obter uma resposta melhor, pois, até onde sei, os planos não fornecerão mais detalhes. É possível usar o PerfView ou outra ferramenta baseada no rastreamento ETW para comparar pilhas de chamadas entre as consultas:
À esquerda, a consulta que faz a verificação do índice em cluster e, à direita, a consulta que faz a classificação. Marquei pilhas de chamadas em azul ou vermelho que aparecem apenas em uma consulta. Não é de surpreender que as diferentes pilhas de chamadas com um alto número de ciclos de CPU amostrados para a consulta de classificação pareçam ter a ver com as leituras lógicas necessárias para executar a atualização no índice em cluster. Além disso, existem diferenças no número de ciclos amostrados entre as consultas para a mesma operação. Por exemplo, a consulta com a classificação gasta 31 ciclos adquirindo travas, enquanto a consulta com a varredura gasta apenas 9 ciclos adquirindo travas.
Suspeito que o SQL Server esteja escolhendo o plano mais lento devido a uma limitação de custo do operador do plano de consulta. Talvez parte da diferença no tempo de execução seja devido ao hardware ou à sua edição do SQL Server. De qualquer forma, o SQL Server não é capaz de descobrir que a coluna da data está implicitamente ordenada exatamente da mesma forma que o índice clusterizado. Os dados são retornados da varredura de índice em cluster na ordem das chaves em cluster, portanto, não há necessidade de executar uma classificação na tentativa de otimizar a E / S ao fazer a atualização do índice em cluster.