Lors de la compilation et de l'exécution des requêtes, le serveur SQL ne prend pas le temps de déterminer si une instruction UPDATE va effectivement modifier des valeurs ou non. Il effectue simplement les écritures comme prévu, même si elles ne sont pas nécessaires.
Dans un scénario comme
update table1 set col1 = 'hello'
vous pourriez penser que SQL ne fera rien, mais il le fera - il effectuera toutes les écritures nécessaires comme si vous aviez réellement modifié la valeur. Cela se produit à la fois pour la table physique (ou l'index clusterisé) et pour tout index non clusterisé défini sur cette colonne. Cela entraîne des écritures dans les tables/index physiques, le recalcul des index et des écritures dans le journal des transactions. Lorsque l'on travaille avec de grands ensembles de données, il y a d'énormes avantages en termes de performances à ne mettre à jour que les lignes qui recevront un changement.
Si nous voulons éviter le surcoût de ces écritures lorsque cela n'est pas nécessaire, nous devons trouver un moyen de vérifier la nécessité d'une mise à jour. Une façon de vérifier la nécessité d'une mise à jour serait d'ajouter quelque chose comme "where col <> 'hello'.
update table1 set col1 = 'hello' where col1 <> 'hello'
Mais cette méthode n'est pas très efficace dans certains cas, par exemple si vous mettez à jour plusieurs colonnes dans un tableau comportant de nombreuses lignes et que seul un petit sous-ensemble de ces lignes voit ses valeurs modifiées. Cela est dû à la nécessité de filtrer ensuite sur toutes ces colonnes, et les prédicats de non-égalité ne sont généralement pas en mesure d'utiliser les recherches d'index, et la surcharge des écritures de table et d'index et des entrées de journal de transaction comme mentionné ci-dessus.
Mais il existe une bien meilleure solution, qui consiste à combiner une clause EXISTS et une clause EXCEPT. L'idée est de comparer les valeurs de la ligne de destination à celles de la ligne source correspondante pour déterminer si une mise à jour est réellement nécessaire. Regardez la requête modifiée ci-dessous et examinez le filtre de requête supplémentaire commençant par EXISTS. Notez qu'à l'intérieur de la clause EXISTS, les instructions SELECT n'ont pas de clause FROM. Cette partie est particulièrement importante car cela ne fait qu'ajouter un balayage constant et une opération de filtrage supplémentaires dans le plan de requête (le coût des deux est trivial). Vous obtenez ainsi une méthode très légère pour déterminer si une mise à jour est nécessaire en premier lieu, en évitant les surcharges d'écriture inutiles.
update table1 set col1 = 'hello'
/* AVOID NET ZERO CHANGES */
where exists
(
/* DESTINATION */
select table1.col1
except
/* SOURCE */
select col1 = 'hello'
)
Cela semble excessivement compliqué par rapport à la vérification des mises à jour dans une simple clause WHERE pour le scénario simple de la question initiale, lorsque vous mettez à jour une valeur pour toutes les lignes d'une table avec une valeur littérale. Cependant, cette technique fonctionne très bien si vous mettez à jour plusieurs colonnes dans une table, que la source de votre mise à jour est une autre requête et que vous voulez minimiser les écritures et les entrées dans les journaux de transactions. Elle est également plus performante que le test de chaque champ avec <>.
Un exemple plus complet pourrait être
update table1
set col1 = 'hello',
col2 = 'hello',
col3 = 'hello'
/* Only update rows from CustomerId 100, 101, 102 & 103 */
where table1.CustomerId IN (100, 101, 102, 103)
/* AVOID NET ZERO CHANGES */
and exists
(
/* DESTINATION */
select table1.col1
table1.col2
table1.col3
except
/* SOURCE */
select z.col1,
z.col2,
z.col3
from #anytemptableorsubquery z
where z.CustomerId = table1.CustomerId
)