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

Comprendre le problème de lecture sale avec SQL Server

L'un des problèmes les plus courants qui se produisent lors de l'exécution de transactions simultanées est le problème Dirty Read. Une lecture sale se produit lorsqu'une transaction est autorisée à lire des données qui sont modifiées par une autre transaction qui s'exécute simultanément mais qui ne s'est pas encore validée.

Si la transaction qui modifie les données se commet elle-même, le problème de lecture sale ne se produit pas. Cependant, si la transaction qui modifie les données est annulée après que l'autre transaction a lu les données, cette dernière transaction contient des données modifiées qui n'existent pas réellement.

Comme toujours, assurez-vous d'être bien sauvegardé avant d'expérimenter un nouveau code. Consultez cet article sur la sauvegarde des bases de données MS SQL si vous n'êtes pas sûr.

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 le produit.

Le tableau ressemble à ceci :

[identifiant de table=20 /]

Supposons que vous disposiez d'un système en ligne dans lequel un utilisateur peut acheter des produits et afficher des produits en même temps. Jetez un oeil à la figure suivante.

Imaginez un scénario dans lequel un utilisateur essaie d'acheter un produit. La transaction 1 effectuera la tâche d'achat pour l'utilisateur. La première étape de la transaction consistera à mettre à jour les articles en stock.

Avant la transaction, il y a 12 articles en stock; la transaction le mettra à jour à 11. La transaction communiquera désormais avec une passerelle de facturation externe.

Si à ce moment-là, une autre transaction, disons Transaction 2, lit ItemsInStock pour les ordinateurs portables, elle lira 11. Cependant, si par la suite, l'utilisateur derrière la Transaction 1 s'avère avoir des fonds insuffisants sur son compte, la Transaction 1 sera lancée retour et la valeur de la colonne ItemsInStock reviendra à 12.

Cependant, la transaction 2 a 11 comme valeur pour la colonne ItemsInStock. Ce sont des données sales et le problème est appelé problème de lecture sale.

Exemple de travail de problème de lecture sale

Jetons un coup d'œil au problème de lecture sale en action dans SQL Server. Comme toujours, créons d'abord notre table et ajoutons-y des données factices. Exécutez le script suivant sur votre serveur de base de données.

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, 'iPhone', 15),
(3, 'Tablets', 10)

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.

USE pos;

SELECT * FROM products

-- Transaction 1

BEGIN Tran

UPDATE products set ItemsInStock = 11
WHERE Id = 1

-- Billing the customer
WaitFor Delay '00:00:10'
Rollback Transaction

Dans le script ci-dessus, nous commençons une nouvelle transaction qui met à jour la valeur de la colonne "ItemsInStock" de la table des produits où Id vaut 1. Nous simulons ensuite le délai de facturation du client en utilisant les fonctions "WaitFor" et "Delay". Un délai de 10 secondes a été défini dans le script. Après cela, nous annulons simplement la transaction.

Dans la deuxième instance de SSMS, nous ajoutons simplement l'instruction SELECT suivante.

USE pos;

-- Transaction 2

SELECT * FROM products
WHERE Id = 1

Maintenant, exécutez d'abord la première transaction, c'est-à-dire exécutez le script dans la première instance de SSMS, puis exécutez immédiatement le script dans la deuxième instance de SSMS.

Vous verrez que les deux transactions continueront à s'exécuter pendant 10 secondes et après cela, vous verrez que la valeur de la colonne "ItemsInStock" pour l'enregistrement avec l'ID 1 est toujours 12, comme indiqué par la deuxième transaction. Bien que la première transaction l'ait mis à jour à 11, ait attendu 10 secondes, puis l'ait ramené à 12, la valeur affichée par la deuxième transaction est 12 au lieu de 11.

Ce qui s'est réellement passé, c'est que lorsque nous avons exécuté la première transaction, la valeur de la colonne "ItemsinStock" a été mise à jour. Il a ensuite attendu 10 secondes, puis a annulé la transaction.

Bien que nous ayons commencé la deuxième transaction immédiatement après la première, elle a dû attendre la fin de la première transaction. C'est pourquoi la deuxième transaction a également attendu 10 secondes et pourquoi la deuxième transaction s'est exécutée immédiatement après la fin de l'exécution de la première transaction.

Lire le niveau d'isolement validé

Pourquoi la transaction 2 a-t-elle dû attendre la fin de la transaction 1 avant de s'exécuter ?

La réponse est que le niveau d'isolement par défaut entre les transactions est « read commited ». Le niveau d'isolement Read Committed garantit que les données ne peuvent être lues par une transaction que si elle est dans l'état validé.

Dans notre exemple, la transaction 1 a mis à jour les données mais ne les a pas validées tant qu'elles n'ont pas été annulées. C'est pourquoi la transaction 2 a dû attendre que la transaction 1 valide les données ou annule la transaction avant de pouvoir lire les données.

Maintenant, dans des scénarios pratiques, nous avons souvent plusieurs transactions en cours sur une seule base de données en même temps et nous ne voulons pas que chaque transaction doive attendre son tour. Cela peut rendre les bases de données très lentes. Imaginez que vous achetiez quelque chose en ligne à partir d'un grand site Web qui ne pouvait traiter qu'une seule transaction à la fois !

Lecture des données non validées

La réponse à ce problème est de permettre à vos transactions de fonctionner avec des données non validées.

Pour lire des données non validées, définissez simplement le niveau d'isolement de la transaction sur « lecture non validée ». Mettez à jour la transaction 2 en ajoutant un niveau d'isolement conformément au script ci-dessous.

USE pos;

-- Transaction 2
set transaction isolation level read uncommitted

SELECT * FROM products
WHERE Id = 1

Maintenant, si vous exécutez la transaction 1, puis exécutez immédiatement la transaction 2, vous verrez que la transaction 2 n'attendra pas que la transaction 1 valide les données. La transaction 2 lira immédiatement les données modifiées. Ceci est illustré dans la figure suivante :

Ici, l'instance de gauche exécute la transaction 1 et l'instance de droite exécute la transaction 2.

Nous exécutons d'abord la transaction 1 qui met à jour la valeur de "ItemsinStock" pour l'id 1 à 11 à partir de 12, puis attend 10 secondes avant d'être annulée.

Pendant ce temps, la transaction w lit les données modifiées qui sont 11, comme indiqué dans la fenêtre de résultat à droite. Étant donné que la transaction 1 est annulée, il ne s'agit pas de la valeur réelle dans la table. La valeur réelle est 12. Essayez d'exécuter à nouveau la transaction 2 et vous verrez que cette fois, elle récupère 12.

La lecture non validée est le seul niveau d'isolement qui présente le problème de lecture incorrecte. Ce niveau d'isolement est le moins restrictif de tous les niveaux d'isolement et permet de lire des données non validées.

Évidemment, il y a des avantages et des inconvénients à utiliser Read Uncommitted, cela dépend de l'application pour laquelle votre base de données est utilisée. Évidemment, ce serait une très mauvaise idée de l'utiliser pour la base de données derrière un système ATM et d'autres systèmes très sécurisés. Cependant, pour les applications où la vitesse est très importante (exploitation de grands magasins de commerce électronique), l'utilisation de la lecture non validée est plus logique.