J'ai simulé des données de test qui reproduisent principalement votre problème:
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]);
Statistiques pour la requête qui utilise l'index non cluster:
Tableau «TestTable». Nombre de balayages 1, lectures logiques 1299838, lectures physiques 0, lectures en lecture anticipée 0, lectures logiques en lob 0, lectures physiques en lob 0, lobes en lecture anticipée 0.
Temps d'exécution SQL Server: temps CPU = 984 ms, temps écoulé = 988 ms.
Statistiques pour la requête qui utilise l'index cluster:
Tableau «TestTable». Nombre de balayages 1, lectures logiques 72609, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Temps d'exécution SQL Server: temps CPU = 781 ms, temps écoulé = 772 ms.
Accéder à votre question:
Est-il possible de profiter de ce fait pour améliorer les performances de ma requête?
Oui. Vous pouvez utiliser l'index non cluster dont vous disposez déjà pour trouver efficacement la id
valeur maximale à mettre à jour. Si vous enregistrez cela dans une variable et que vous le filtrez, vous obtenez un plan de requête pour la mise à jour qui effectue l'analyse d'index en cluster (sans le tri) qui s'arrête tôt et fait donc moins d'E / S. Voici une implémentation:
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);
Exécutez les statistiques pour la nouvelle requête:
Tableau «TestTable». Nombre de balayages 1, lectures logiques 3, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Tableau «TestTable». Nombre de balayages 1, lectures logiques 4776, lectures physiques 0, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.
Temps d'exécution SQL Server: temps CPU = 515 ms, temps écoulé = 510 ms.
Ainsi que le plan de requête:

Cela dit, votre désir d'accélérer la requête me suggère que vous prévoyez d'exécuter la requête plus d'une fois. À l'heure actuelle, votre requête a un filtre à composition non limitée sur la date
colonne. Est-il vraiment nécessaire d'anonymiser les lignes plus d'une fois? Pouvez-vous éviter de mettre à jour ou d'analyser des lignes déjà anonymisées? Il devrait certainement être plus rapide de mettre à jour une plage de dates avec des dates des deux côtés. Vous pouvez également ajouter la Anonymised
colonne à votre index, mais cet index devra être mis à jour lors de votre UPDATE
requête. En résumé, évitez de traiter les mêmes données encore et encore si vous le pouvez.
La requête d'origine que vous avez avec le tri est plus lente en raison du travail effectué dans l' Clustered Index Update
opérateur. Le temps passé sur la recherche d'index et le tri n'est que de 407 ms. Vous pouvez le voir dans le plan réel. Le plan s'exécute en mode ligne, le temps passé sur le tri est donc le temps de cet opérateur avec chaque opérateur enfant:

Cela laisse l'opérateur de tri avec environ 1600 ms de temps. SQL Server doit lire les pages de l'index cluster pour effectuer la mise à jour. Vous pouvez voir que l' Clustered Index Update
opérateur effectue des lectures logiques 1205921. Vous pouvez en savoir plus sur les optimisations de tri pour DML et la prélecture optimisée dans cet article de blog de Paul White .
L'autre plan de requête que vous avez (sans le tri) prend 683 ms pour l'analyse d'index cluster et environ 550 ms pour l' Clustered Index Update
opérateur. L'opérateur de mise à jour ne fait pas d'E / S pour cette requête.
La réponse simple pour expliquer pourquoi le plan avec le tri est plus lent est que SQL Server effectue des lectures plus logiques sur l'index cluster pour ce plan par rapport au plan d'analyse d'index cluster. Même si toutes les données nécessaires sont en mémoire, il y a toujours un surcoût et un coût pour effectuer ces lectures logiques. Une meilleure réponse est beaucoup plus difficile à obtenir, dans la mesure où je sais que les plans ne vous donneront pas plus de détails. Il est possible d'utiliser PerfView ou un autre outil basé sur le traçage ETW pour comparer les piles d'appels entre les requêtes:

À gauche se trouve la requête qui effectue l'analyse d'index en cluster et à droite se trouve la requête qui effectue le tri. J'ai marqué les piles d'appels en bleu ou en rouge qui n'apparaissent que dans une seule requête. Sans surprise, les différentes piles d'appels avec un nombre élevé de cycles CPU échantillonnés pour la requête de tri semblent avoir à voir avec les lectures logiques requises pour effectuer la mise à jour sur l'index clusterisé. De plus, il existe des différences dans le nombre de cycles échantillonnés entre les requêtes pour la même opération. Par exemple, la requête avec le tri passe 31 cycles à acquérir des verrous tandis que la requête avec l'analyse ne passe que 9 cycles à acquérir des verrous.
Je soupçonne que SQL Server choisit le plan le plus lent en raison d'une limitation des coûts de l'opérateur du plan de requête. Peut-être qu'une partie de la différence de temps d'exécution est due au matériel ou à votre édition de SQL Server. Dans tous les cas, SQL Server n'est pas en mesure de comprendre que la colonne de date est implicitement ordonnée exactement de la même manière que l'index cluster. Les données sont renvoyées à partir de l'analyse d'index en cluster dans l'ordre des clés en cluster, il n'est donc pas nécessaire d'effectuer un tri pour tenter d'optimiser les E / S lors de la mise à jour de l'index en cluster.