Het probleem met de verloren update treedt op wanneer 2 gelijktijdige transacties dezelfde gegevens proberen te lezen en bij te werken. Laten we dit begrijpen aan de hand van een voorbeeld.
Stel dat we een tabel hebben met de naam "Product" die de id, naam en ItemsinStock voor een product opslaat.
Het wordt gebruikt als onderdeel van een online systeem dat het aantal artikelen in voorraad voor een bepaald product weergeeft en moet dus worden bijgewerkt telkens wanneer een verkoop van dat product wordt gedaan.
De tabel ziet er als volgt uit:
Id | Naam | ItemsinStock |
1 | Laptops | 12 |
Overweeg nu een scenario waarin een gebruiker arriveert en het proces van het kopen van een laptop start. Hiermee wordt een transactie gestart. Laten we deze transactie transactie 1 noemen.
Tegelijkertijd logt een andere gebruiker in op het systeem en initieert een transactie, laten we deze transactie 2 noemen. Bekijk de volgende afbeelding.
Transactie 1 leest de items in voorraad voor laptops die 12 is. Iets later leest transactie 2 de waarde voor ItemsinStock voor laptops die op dit moment nog 12 is. Transactie 2 verkoopt vervolgens drie laptops, kort voordat transactie 1 2 items verkoopt.
Transactie 2 voltooit dan eerst de uitvoering en update ItemsinStock naar 9 aangezien het drie van de 12 laptops heeft verkocht. Transactie 1 verbindt zich. Aangezien transactie 1 twee items heeft verkocht, wordt ItemsinStock bijgewerkt naar 10.
Dit is onjuist, het juiste cijfer is 12-3-2 =7
Werkend voorbeeld van een verloren update-probleem
Laten we eens kijken naar het verloren update-probleem in actie in SQL Server. Zoals altijd zullen we eerst een tabel maken en er wat dummy-gegevens aan toevoegen.
Zorg er zoals altijd voor dat je een goede back-up hebt gemaakt voordat je met nieuwe code gaat spelen. Raadpleeg dit artikel over back-up van SQL Server als u het niet zeker weet.
Voer het volgende script uit op uw databaseserver.
<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>
Open nu twee SQL Server Management Studio-instanties naast elkaar. We zullen in elk van deze gevallen één transactie uitvoeren.
Voeg het volgende script toe aan de eerste instantie van 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>
Dit is het script voor transactie 1. Hier beginnen we de transactie en declareren een integer type variabele "@ItemsInStock". De waarde van deze variabele wordt ingesteld op de waarde van de kolom ItemsinStock voor het record met Id 1 uit de productentabel. Vervolgens wordt een vertraging van 12 seconden toegevoegd, zodat transactie 2 de uitvoering vóór transactie 1 kan voltooien. Na de vertraging wordt de waarde van de variabele @ItemsInStock verlaagd met 2, wat de verkoop van 2 producten betekent.
Ten slotte wordt de waarde voor de kolom ItemsinStock voor het record met Id 1 bijgewerkt met de waarde van de variabele @ItemsInStock. We printen dan de waarde van de @ItemsInStock variabele op het scherm en voeren de transactie uit.
In het tweede exemplaar van SSMS voegen we het script voor transactie 2 toe, dat als volgt is:
<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>
Het script voor transactie 2 is vergelijkbaar met transactie 1. Hier in transactie 2 is de vertraging echter slechts drie seconden en is de verlaging van de waarde voor de variabele @ItemsInStock drie, aangezien het een verkoop van drie artikelen is.
Voer nu transactie 1 en vervolgens transactie 2 uit. U zult zien dat transactie 2 eerst de uitvoering voltooit. En de waarde die wordt afgedrukt voor de variabele @ItemsInStock is 9. Na enige tijd voltooit transactie 1 ook de uitvoering en de waarde die wordt afgedrukt voor de variabele @ItemsInStock is 10.
Beide waarden zijn onjuist, de werkelijke waarde voor de kolom ItemsInStock voor het product met ID 1 moet 7 zijn.
OPMERKING:
Het is belangrijk om hier op te merken dat het probleem met de verloren update alleen optreedt met lees-committe en lees-niet-vastgelegde transactie-isolatieniveaus. Bij alle andere transactie-isolatieniveaus doet dit probleem zich niet voor.
Isolatieniveau voor herhaalbare transacties lezen
Laten we het isolatieniveau voor beide transacties bijwerken om herhaalbaar te zijn en kijken of het probleem met de verloren update optreedt. Maar voer eerst het volgende statement uit om de waarde voor ItemsInStock weer bij te werken naar 12.
Update products SET ItemsinStock = 12
Script voor transactie 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 voor transactie 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 in beide transacties hebben we het isolatieniveau ingesteld op herhaalbaar lezen.
Voer nu transactie 1 uit en voer vervolgens onmiddellijk transactie 2 uit. In tegenstelling tot het vorige geval zal transactie 2 moeten wachten tot transactie 1 zichzelf vastlegt. Daarna treedt de volgende fout op voor transactie 2:
Msg 1205, Niveau 13, Staat 51, Lijn 15
Transactie (proces-ID 55) was vastgelopen op vergrendelingsbronnen met een ander proces en is gekozen als het slachtoffer van de impasse. Voer de transactie opnieuw uit.
Deze fout treedt op omdat herhaalbaar lezen de bron vergrendelt die wordt gelezen of bijgewerkt door transactie 1 en een impasse creëert voor de andere transactie die toegang probeert te krijgen tot dezelfde bron.
De fout zegt dat transactie 2 een impasse heeft op een resource met een ander proces en dat deze transactie is geblokkeerd door de deadlock. Dit betekent dat de andere transactie toegang heeft gekregen tot de bron terwijl deze transactie is geblokkeerd en geen toegang heeft gekregen tot de bron.
Er staat ook dat de transactie opnieuw moet worden uitgevoerd omdat de bron nu gratis is. Als u nu transactie 2 opnieuw uitvoert, ziet u de juiste waarde van artikelen in voorraad, d.w.z. 7. Dit komt omdat transactie 1 de IteminStock-waarde al met 2 had verlaagd, transactie 2 verlaagt dit verder met 3, dus 12 - (2+ 3) =7.