-
Vous ne devez pas mettre à jour 10 000 lignes dans un ensemble, sauf si vous êtes certain que l'opération obtient des verrous de page (car plusieurs lignes par page font partie de la
UPDATE
opération). Le problème est que l'escalade de verrous (des verrous de ligne ou de page vers le tableau) se produit à 5 000 verrous . Il est donc plus sûr de le maintenir juste en dessous de 5000, juste au cas où l'opération utilise des verrous de ligne. -
Vous ne devriez pas utilisez SET ROWCOUNT pour limiter le nombre de lignes qui seront modifiées. Il y a deux problèmes ici :
-
Il est obsolète depuis la sortie de SQL Server 2005 (il y a 11 ans) :
L'utilisation de SET ROWCOUNT n'affectera pas les instructions DELETE, INSERT et UPDATE dans une future version de SQL Server. Évitez d'utiliser SET ROWCOUNT avec les instructions DELETE, INSERT et UPDATE dans les nouveaux travaux de développement et prévoyez de modifier les applications qui l'utilisent actuellement. Pour un comportement similaire, utilisez la syntaxe TOP
-
Cela peut affecter plus que la seule déclaration à laquelle vous avez affaire :
La définition de l'option SET ROWCOUNT entraîne l'arrêt du traitement de la plupart des instructions Transact-SQL lorsqu'elles ont été affectées par le nombre de lignes spécifié. Cela inclut les déclencheurs. L'option ROWCOUNT n'affecte pas les curseurs dynamiques, mais elle limite le jeu de lignes de jeu de clés et les curseurs insensibles. Cette option doit être utilisée avec prudence.
Utilisez plutôt le
TOP ()
clause. -
-
Il n'y a aucun but à avoir une transaction explicite ici. Cela complique le code et vous n'avez aucune manipulation pour un
ROLLBACK
, ce qui n'est même pas nécessaire puisque chaque instruction est sa propre transaction (c'est-à-dire une validation automatique). -
En supposant que vous trouviez une raison de conserver la transaction explicite, vous n'avez pas de
TRY
/CATCH
structure. Veuillez consulter ma réponse sur DBA.StackExchange pour unTRY
/CATCH
modèle qui gère les transactions :Sommes-nous obligés de gérer Transaction en code C# ainsi qu'en procédure Store
Je soupçonne que le vrai WHERE
la clause n'est pas affichée dans l'exemple de code de la question, donc simplement en s'appuyant sur ce qui a été montré, c'est mieux modèle serait :
DECLARE @Rows INT,
@BatchSize INT; -- keep below 5000 to be safe
SET @BatchSize = 2000;
SET @Rows = @BatchSize; -- initialize just to enter the loop
BEGIN TRY
WHILE (@Rows = @BatchSize)
BEGIN
UPDATE TOP (@BatchSize) tab
SET tab.Value = 'abc1'
FROM TableName tab
WHERE tab.Parameter1 = 'abc'
AND tab.Parameter2 = 123
AND tab.Value <> 'abc1' COLLATE Latin1_General_100_BIN2;
-- Use a binary Collation (ending in _BIN2, not _BIN) to make sure
-- that you don't skip differences that compare the same due to
-- insensitivity of case, accent, etc, or linguistic equivalence.
SET @Rows = @@ROWCOUNT;
END;
END TRY
BEGIN CATCH
RAISERROR(stuff);
RETURN;
END CATCH;
En testant @Rows
contre @BatchSize
, vous pouvez éviter cette dernière UPDATE
requête (dans la plupart des cas) car le jeu final est généralement un certain nombre de lignes inférieur à @BatchSize
, auquel cas nous savons qu'il n'y a plus rien à traiter (c'est ce que vous voyez dans la sortie indiquée dans votre réponse). Uniquement dans les cas où l'ensemble final de lignes est égal à @BatchSize
ce code exécutera-t-il une dernière UPDATE
affectant 0 lignes.
J'ai également ajouté une condition au WHERE
clause pour empêcher les lignes qui ont déjà été mises à jour d'être mises à jour à nouveau.
REMARQUE CONCERNANT LES PERFORMANCES
J'ai souligné "mieux" ci-dessus (comme dans "c'est un meilleur model") car cela a plusieurs améliorations par rapport au code original de l'OP, et fonctionne bien dans de nombreux cas, mais n'est pas parfait pour tous les cas. Pour les tables d'au moins une certaine taille (qui varie en raison de plusieurs facteurs, je peux donc ' Pour être plus précis), les performances se dégraderont car il y aura moins de lignes à corriger si :
- il n'y a pas d'index pour prendre en charge la requête, ou
- il y a un index, mais au moins une colonne dans
WHERE
clause est un type de données chaîne qui n'utilise pas de classement binaire, d'où unCOLLATE
La clause est ajoutée à la requête ici pour forcer le classement binaire, et cela invalide l'index (pour cette requête particulière).
C'est la situation que @mikesigs a rencontrée, nécessitant donc une approche différente. La méthode mise à jour copie les ID de toutes les lignes à mettre à jour dans une table temporaire, puis utilise cette table temporaire pour INNER JOIN
à la table en cours de mise à jour sur la ou les colonnes de clé d'index cluster. (Il est important de capturer et de joindre sur l'index cluster colonnes, qu'il s'agisse ou non de colonnes de clé primaire !).
Veuillez consulter la réponse de @mikesigs ci-dessous pour plus de détails. L'approche montrée dans cette réponse est un modèle très efficace que j'ai moi-même utilisé à de nombreuses reprises. Les seuls changements que je ferais sont :
- Créer explicitement le
#targetIds
table plutôt que d'utiliserSELECT INTO...
- Pour les
#targetIds
table, déclarez une clé primaire clusterisée sur la ou les colonnes. - Pour les
#batchIds
table, déclarez une clé primaire clusterisée sur la ou les colonnes. - Pour insertion dans
#targetIds
, utilisezINSERT INTO #targetIds (column_name(s)) SELECT
et supprimer leORDER BY
car c'est inutile.
Donc, si vous n'avez pas d'index pouvant être utilisé pour cette opération et que vous ne pouvez pas en créer temporairement un qui fonctionnera réellement (un index filtré peut fonctionner, selon votre WHERE
clause pour la UPDATE
requête), puis essayez l'approche indiquée dans la réponse de @mikesigs (et si vous utilisez cette solution, veuillez voter pour).