Pratique SQL Server

Éviter les modifications perdues avec rowversion

Utilisez rowversion pour protéger les formulaires SQL Server, détecter les écritures concurrentes et gérer les conflits sans perdre le travail saisi.

Deux personnes ouvrent la même fiche produit. La première corrige une indication de prix et enregistre. La seconde modifie la ponctuation dans une ancienne copie et renvoie tout le formulaire. Les deux requêtes réussissent, mais la seconde efface la première correction. Une transaction autour de chaque UPDATE ne suffit pas : la lecture devenue obsolète a eu lieu auparavant.

Vérifier la version dans la requête de modification

La demande doit préciser quelle version l'utilisateur a consultée. Une colonne rowversion fournit un jeton binaire de huit octets généré par la base. Renvoyez ce jeton avec les champs modifiables, puis exigez-le lors de l'enregistrement. La clé primaire et le jeton attendu doivent figurer ensemble dans le prédicat du même UPDATE.

Lire le jeton, le comparer dans l'application puis exécuter une modification inconditionnelle laisse une fenêtre de concurrence. Un autre utilisateur peut écrire entre ces opérations. Cet exemple simule deux éditeurs dans une seule connexion. Tous deux mémorisent la version initiale ; après la première modification, le second prédicat ne correspond plus.

CREATE TABLE #Draft
(
    DraftId int NOT NULL PRIMARY KEY,
    Title nvarchar(100) NOT NULL,
    Revision rowversion NOT NULL
);
INSERT #Draft (DraftId, Title) VALUES (1, N'Initial title');

DECLARE @SeenByA binary(8), @SeenByB binary(8);
SELECT @SeenByA = Revision, @SeenByB = Revision
FROM #Draft WHERE DraftId = 1;

UPDATE #Draft SET Title = N'Editor A'
OUTPUT inserted.DraftId, inserted.Title, inserted.Revision
WHERE DraftId = 1 AND Revision = @SeenByA;

UPDATE #Draft SET Title = N'Editor B'
WHERE DraftId = 1 AND Revision = @SeenByB;
DECLARE @Changed int = @@ROWCOUNT;

SELECT @Changed AS RowsChanged;
SELECT DraftId, Title, Revision FROM #Draft;
DROP TABLE #Draft;

Le second UPDATE modifie zéro ligne et le titre reste Editor A. Le premier OUTPUT renvoie le nouveau jeton utilisable dans la réponse de succès. Avec @@ROWCOUNT, conservez immédiatement le résultat, car une autre instruction peut le remplacer. La clé primaire garantit qu'une demande ne peut modifier plus d'une ligne.

Traitez le jeton comme une valeur binaire opaque. Une API JSON peut choisir Base64 ou une représentation hexadécimale de longueur fixe. Décodez exactement huit octets et utilisez un paramètre binaire. Ne convertissez pas le jeton en nombre JavaScript. Ce n'est pas une date et le client ne doit pas calculer sa valeur suivante. Une date de modification lisible nécessite une colonne datetime2 distincte.

Préserver le travail lors du conflit

Zéro ligne modifiée indique que la combinaison clé-version n'était pas disponible. La ligne peut avoir été modifiée, supprimée, ou être hors du périmètre autorisé. Le résultat seul ne distingue pas ces situations. Incluez les restrictions d'accès dans la requête ou dans un contrôle transactionnel équivalent. Une requête de diagnostic ne doit pas révéler une fiche inaccessible.

Pour une personne autorisée, conservez les valeurs soumises avant de récupérer la fiche actuelle. Une interface de résolution peut présenter la version originale, la proposition et la version courante. Recharger la page en supprimant le texte saisi protège la base, mais déplace le problème vers l'utilisateur.

Ne réessayez pas automatiquement une sauvegarde complète avec le jeton le plus récent. Cela revient à écraser silencieusement l'autre modification. Certains besoins se modélisent autrement : incrémenter un compteur de façon atomique évite de remplacer un total lu auparavant. Le contrat doit exprimer l'intention réelle, pas transformer toutes les commandes en sauvegardes de document.

Étendre le contrat aux opérations composées

Un jeton protège une ligne. Une page qui modifie un en-tête de commande et plusieurs lignes ne détectera pas une modification indépendante de ligne en vérifiant seulement l'en-tête. Il faut soit faire évoluer une version commune lors de chaque changement pertinent, soit comparer les jetons des lignes concernées dans une transaction. Si l'opération doit être indivisible, un seul conflit impose son annulation complète.

Les déclencheurs nécessitent également un essai d'intégration. OUTPUT expose les valeurs avant les déclencheurs AFTER. Un déclencheur qui réécrit la même ligne peut changer le jeton une nouvelle fois. L'application doit alors lire le jeton final dans la transaction après son exécution. Attendez le commit avant de confirmer la réussite ; une ligne OUTPUT ne prouve pas la validation transactionnelle.

Testez deux sauvegardes simultanées, une suppression suivie d'une sauvegarde et une nouvelle tentative après perte de réponse réseau. Mesurez les conflits séparément des erreurs serveur. Leur fréquence peut révéler des formulaires qui remplacent trop de champs ou des tâches de fond qui réécrivent inutilement les lignes. Le mécanisme devient ainsi un moyen de comprendre les collisions entre intentions métier.

Références techniques: Microsoft Learn: rowversion · Microsoft Learn: OUTPUT clause.

Question sur cet article

Vous avez une question sur ce sujet ?

Expliquez ce que vous évaluez ou le point qui vous bloque. Nous vous répondrons avec une recommandation pratique.

Inquiries are not enabled in this preview.

Poser une question sur cet article