Database
 sql >> Base de données >  >> RDS >> Database

Le problème de perte de mise à jour dans les transactions simultanées

Le problème de perte de mise à jour se produit lorsque 2 transactions simultanées tentent de lire et de mettre à jour les mêmes données. Comprenons cela à l'aide d'un exemple.

Supposons que nous ayons une table nommée "Produit" qui stocke l'identifiant, le nom et les articles en stock pour un produit.

Il est utilisé dans le cadre d'un système en ligne qui affiche le nombre d'articles en stock pour un produit particulier et doit donc être mis à jour chaque fois qu'une vente de ce produit est effectuée.

Le tableau ressemble à ceci :

Identifiant

Nom

Articlesenstock

1

Ordinateurs portables

12

Considérons maintenant un scénario dans lequel un utilisateur arrive et lance le processus d'achat d'un ordinateur portable. Cela lancera une transaction. Appelons cette transaction, transaction 1.

Au même moment, un autre utilisateur se connecte au système et lance une transaction, appelons cette transaction 2. Jetez un œil à la figure suivante.

La transaction 1 lit les articles en stock pour les ordinateurs portables, soit 12. Un peu plus tard, la transaction 2 lit la valeur des articles en stock pour les ordinateurs portables, qui sera toujours de 12 à ce stade. La transaction 2 vend alors trois ordinateurs portables, peu de temps avant que la transaction 1 ne vende 2 articles.

La transaction 2 achèvera alors son exécution en premier et mettra à jour ItemsinStock à 9 puisqu'elle a vendu trois des 12 ordinateurs portables. La transaction 1 s'engage. Puisque la transaction 1 a vendu deux articles, elle met à jour ItemsinStock à 10.

Ceci est incorrect, le chiffre correct est 12-3-2 =7

Exemple concret de problème de perte de mise à jour

Jetons un coup d'œil au problème de mise à jour perdue en action dans SQL Server. Comme toujours, nous allons d'abord créer une table et y ajouter des données factices.

Comme toujours, assurez-vous que vous êtes correctement sauvegardé avant de jouer avec un nouveau code. Si vous n'êtes pas sûr, consultez cet article sur la sauvegarde SQL Server.

Exécutez le script suivant sur votre serveur de base de données.

<span style="font-size: 14px;">CREATE DATABASE pos;

USE pos;

CREATE TABLE products
(
	Id INT PRIMARY KEY,
	Name VARCHAR(50) NOT NULL,
	ItemsinStock INT NOT NULL

)

INSERT into products

VALUES 
(1, 'Laptop', 12),
(2, 'Iphon', 15),
(3, 'Tablets', 10)</span>

Maintenant, ouvrez deux instances SQL Server Management Studio côte à côte. Nous exécuterons une transaction dans chacune de ces instances.

Ajoutez le script suivant à la première instance de SSMS.

<span style="font-size: 14px;">USE pos;

-- Transaction 1

BEGIN TRAN

DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:12'
SET @ItemsInStock = @ItemsInStock - 2

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Voici le script de la transaction 1. Ici, nous commençons la transaction et déclarons une variable de type entier "@ItemsInStock". La valeur de cette variable est définie sur la valeur de la colonne ItemsinStock pour l'enregistrement avec l'ID 1 de la table des produits. Ensuite, un délai de 12 secondes est ajouté afin que la transaction 2 puisse terminer son exécution avant la transaction 1. Après le délai, la valeur de la variable @ItemsInStock est décrémentée de 2 signifiant la vente de 2 produits.

Enfin, la valeur de la colonne ItemsinStock pour l'enregistrement avec l'ID 1 est mise à jour avec la valeur de la variable @ItemsInStock. Nous imprimons ensuite la valeur de la variable @ItemsInStock à l'écran et validons la transaction.

Dans la deuxième instance de SSMS, nous ajoutons le script pour la transaction 2 qui est le suivant :

<span style="font-size: 14px;">USE pos;

-- Transaction 2

BEGIN TRAN

DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:3'
SET @ItemsInStock = @ItemsInStock - 3

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Le script de la transaction 2 est similaire à la transaction 1. Cependant, ici, dans la transaction 2, le délai n'est que de trois secondes et la décrémentation de la valeur de la variable @ItemsInStock est de trois, car il s'agit d'une vente de trois articles.

Maintenant, exécutez la transaction 1 puis la transaction 2. Vous verrez la transaction 2 terminer son exécution en premier. Et la valeur imprimée pour la variable @ItemsInStock sera 9. Après un certain temps, la transaction 1 terminera également son exécution et la valeur imprimée pour sa variable @ItemsInStock sera 10.

Ces deux valeurs sont fausses, la valeur réelle de la colonne ItemsInStock pour le produit avec l'ID 1 doit être 7.

REMARQUE :

Il est important de noter ici que le problème de perte de mise à jour ne se produit qu'avec les niveaux d'isolement de transaction en lecture validée et en lecture non validée. Avec tous les autres niveaux d'isolation des transactions, ce problème ne se produit pas.

Lire le niveau d'isolement des transactions répétables

Mettons à jour le niveau d'isolement pour les deux transactions en lecture reproductible et voyons si le problème de mise à jour perdue se produit. Mais avant cela, exécutez l'instruction suivante pour mettre à jour la valeur de ItemsInStock à 12.

Update products SET ItemsinStock = 12

Script pour la transaction 1

<span style="font-size: 14px;">USE pos;

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
-- Transaction 1

BEGIN TRAN
DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:12'
SET @ItemsInStock = @ItemsInStock - 2

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Script pour la transaction 2

<span style="font-size: 14px;">USE pos;

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
-- Transaction 2

BEGIN TRAN
DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:3'
SET @ItemsInStock = @ItemsInStock - 3

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Ici, dans les deux transactions, nous avons défini le niveau d'isolement sur lecture répétable.

Exécutez maintenant la transaction 1, puis exécutez immédiatement la transaction 2. Contrairement au cas précédent, la transaction 2 devra attendre que la transaction 1 se valide. Après cela, l'erreur suivante se produit pour la transaction 2 :

Msg 1205, Niveau 13, État 51, Ligne 15

La transaction (ID de processus 55) a été bloquée sur les ressources de verrouillage avec un autre processus et a été choisie comme victime du blocage. Réexécutez la transaction.

Cette erreur se produit car la lecture répétable verrouille la ressource en cours de lecture ou de mise à jour par la transaction 1 et crée un blocage sur l'autre transaction qui tente d'accéder à la même ressource.

L'erreur indique que la transaction 2 a un blocage sur une ressource avec un autre processus et que cette transaction a été bloquée par le blocage. Cela signifie que l'autre transaction a reçu l'accès à la ressource alors que cette transaction était bloquée et n'a pas eu accès à la ressource.

Il dit également de réexécuter la transaction car la ressource est maintenant libre. Maintenant, si vous exécutez à nouveau la transaction 2, vous verrez la valeur correcte des articles en stock, c'est-à-dire 7. C'est parce que la transaction 1 avait déjà décrémenté la valeur IteminStock de 2, la transaction 2 la décrémente encore de 3, donc 12 - (2+ 3) =7.