Database
 sql >> Datenbank >  >> RDS >> Database

Das Problem der verlorenen Aktualisierung bei gleichzeitigen Transaktionen

Das Problem der verlorenen Aktualisierung tritt auf, wenn zwei gleichzeitige Transaktionen versuchen, dieselben Daten zu lesen und zu aktualisieren. Lassen Sie uns dies anhand eines Beispiels verstehen.

Angenommen, wir haben eine Tabelle mit dem Namen „Produkt“, in der die ID, der Name und die Artikel auf Lager für ein Produkt gespeichert sind.

Es wird als Teil eines Online-Systems verwendet, das die Anzahl der auf Lager befindlichen Artikel für ein bestimmtes Produkt anzeigt und daher bei jedem Verkauf dieses Produkts aktualisiert werden muss.

Die Tabelle sieht folgendermaßen aus:

ID

Name

Artikel auf Lager

1

Laptops

12

Stellen Sie sich nun ein Szenario vor, in dem ein Benutzer ankommt und den Kaufvorgang für einen Laptop einleitet. Dadurch wird eine Transaktion eingeleitet. Nennen wir diese Transaktion Transaktion 1.

Gleichzeitig meldet sich ein anderer Benutzer im System an und initiiert eine Transaktion, nennen wir diese Transaktion 2. Sehen Sie sich die folgende Abbildung an.

Transaktion 1 liest den Artikelbestand für Laptops, der 12 beträgt. Etwas später liest Transaktion 2 den Wert für Artikel auf Lager für Laptops, der zu diesem Zeitpunkt noch 12 beträgt. Transaktion 2 verkauft dann drei Laptops, kurz bevor Transaktion 1 2 Artikel verkauft.

Transaktion 2 wird dann zuerst ihre Ausführung abschließen und ItemsinStock auf 9 aktualisieren, da sie drei der 12 Laptops verkauft hat. Transaktion 1 schreibt sich selbst fest. Da Transaktion 1 zwei Artikel verkauft hat, aktualisiert sie ItemsinStock auf 10.

Das ist falsch, die richtige Zahl ist 12-3-2 =7

Arbeitsbeispiel für ein Problem mit verlorenen Updates

Werfen wir einen Blick auf das verlorene Update-Problem in Aktion in SQL Server. Wie immer erstellen wir zuerst eine Tabelle und fügen einige Dummy-Daten hinzu.

Stellen Sie wie immer sicher, dass Sie ordnungsgemäß gesichert sind, bevor Sie mit neuem Code spielen. Wenn Sie sich nicht sicher sind, lesen Sie diesen Artikel zur SQL Server-Sicherung.

Führen Sie das folgende Skript auf Ihrem Datenbankserver aus.

<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>

Öffnen Sie nun zwei SQL Server Management Studio-Instanzen nebeneinander. Wir werden in jedem dieser Fälle eine Transaktion ausführen.

Fügen Sie das folgende Skript zur ersten Instanz von SSMS hinzu.

<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>

Dies ist das Skript für Transaktion 1. Hier beginnen wir die Transaktion und deklarieren eine Variable vom Typ Integer „@ItemsInStock“. Der Wert dieser Variablen wird auf den Wert der Spalte ItemsinStock für den Datensatz mit der ID 1 aus der Produkttabelle gesetzt. Dann wird eine Verzögerung von 12 Sekunden hinzugefügt, damit Transaktion 2 ihre Ausführung vor Transaktion 1 abschließen kann. Nach der Verzögerung wird der Wert der Variablen @ItemsInStock um 2 verringert, was den Verkauf von 2 Produkten bedeutet.

Schließlich wird der Wert für die Spalte ItemsinStock für den Datensatz mit der ID 1 mit dem Wert der Variablen @ItemsInStock aktualisiert. Wir geben dann den Wert der @ItemsInStock-Variablen auf dem Bildschirm aus und schreiben die Transaktion fest.

In der zweiten Instanz von SSMS fügen wir das Skript für Transaktion 2 hinzu, das wie folgt lautet:

<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>

Das Skript für Transaktion 2 ähnelt Transaktion 1. Hier in Transaktion 2 beträgt die Verzögerung jedoch nur drei Sekunden und die Verringerung des Werts für die @ItemsInStock-Variable beträgt drei, da es sich um einen Verkauf von drei Artikeln handelt.

Führen Sie nun Transaktion 1 und dann Transaktion 2 aus. Sie sehen, wie Transaktion 2 zuerst ihre Ausführung abschließt. Und der für die @ItemsInStock-Variable gedruckte Wert ist 9. Nach einiger Zeit wird auch Transaktion 1 ihre Ausführung abschließen und der für ihre @ItemsInStock-Variable gedruckte Wert wird 10 sein.

Beide Werte sind falsch, der tatsächliche Wert für die Spalte ItemsInStock für das Produkt mit der ID 1 sollte 7 sein.

HINWEIS:

Es ist wichtig, hier zu beachten, dass das Problem der verlorenen Aktualisierung nur bei den Transaktionsisolationsstufen Read Committed und Read Uncommitted auftritt. Bei allen anderen Transaktionsisolationsstufen tritt dieses Problem nicht auf.

Isolationsstufe für wiederholbare Transaktionen lesen

Lassen Sie uns die Isolationsstufe für beide Transaktionen so aktualisieren, dass sie wiederholbar gelesen werden, und sehen, ob das Problem mit dem verlorenen Update auftritt. Führen Sie jedoch vorher die folgende Anweisung aus, um den Wert für ItemsInStock wieder auf 12 zu aktualisieren.

Update products SET ItemsinStock = 12

Skript für Transaktion 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>

Skript für Transaktion 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>

Hier haben wir in beiden Transaktionen die Isolationsstufe auf wiederholbares Lesen gesetzt.

Führen Sie nun Transaktion 1 und dann sofort Transaktion 2 aus. Anders als im vorherigen Fall muss Transaktion 2 warten, bis Transaktion 1 sich selbst festschreibt. Danach tritt bei Transaktion 2 folgender Fehler auf:

Nachricht 1205, Ebene 13, Zustand 51, Zeile 15

Die Transaktion (Prozess-ID 55) war bei Sperrressourcen mit einem anderen Prozess blockiert und wurde als Deadlock-Opfer ausgewählt. Führen Sie die Transaktion erneut aus.

Dieser Fehler tritt auf, weil der wiederholbare Lesevorgang die Ressource sperrt, die von Transaktion 1 gelesen oder aktualisiert wird, und einen Deadlock für die andere Transaktion erzeugt, die versucht, auf dieselbe Ressource zuzugreifen.

Der Fehler besagt, dass Transaktion 2 einen Deadlock auf einer Ressource mit einem anderen Prozess hat und dass diese Transaktion durch den Deadlock blockiert wurde. Das bedeutet, dass der anderen Transaktion Zugriff auf die Ressource gewährt wurde, während diese Transaktion blockiert wurde und keinen Zugriff auf die Ressource erhielt.

Es sagt auch, dass die Transaktion erneut ausgeführt werden soll, da die Ressource jetzt frei ist. Wenn Sie nun Transaktion 2 erneut ausführen, sehen Sie den korrekten Wert der auf Lager befindlichen Artikel, d 3) =7.