Een van de meest voorkomende problemen die optreden tijdens het uitvoeren van gelijktijdige transacties is het Dirty Read-probleem. Een vuile lezing vindt plaats wanneer een transactie gegevens mag lezen die worden gewijzigd door een andere transactie die gelijktijdig wordt uitgevoerd maar die zichzelf nog niet heeft vastgelegd.
Als de transactie die de gegevens wijzigt zichzelf vastlegt, treedt het vuile leesprobleem niet op. Als de transactie die de gegevens wijzigt echter wordt teruggedraaid nadat de andere transactie de gegevens heeft gelezen, bevat de laatste transactie vuile gegevens die niet echt bestaan.
Zorg zoals altijd voor een goede back-up voordat u met een nieuwe code gaat experimenteren. Raadpleeg dit artikel over het maken van back-ups van MS SQL-databases als u het niet zeker weet.
Laten we dit begrijpen aan de hand van een voorbeeld. Stel dat we een tabel hebben met de naam 'Product' waarin de id, naam en ItemsinStock voor het product zijn opgeslagen.
De tabel ziet er als volgt uit:
[tabel id=20 /]
Stel dat u een online systeem heeft waar een gebruiker producten kan kopen en tegelijkertijd producten kan bekijken. Bekijk de volgende afbeelding.
Overweeg een scenario waarin een gebruiker een product probeert te kopen. Transactie 1 voert de aankooptaak uit voor de gebruiker. De eerste stap in de transactie is het bijwerken van de ItemsinStock.
Voor de transactie zijn er 12 artikelen in voorraad; de transactie zal dit bijwerken naar 11. De transactie zal nu communiceren met een externe factureringsgateway.
Als op dit moment een andere transactie, laten we zeggen Transactie 2, ItemsInStock voor laptops leest, zal deze 11 zijn. Als vervolgens blijkt dat de gebruiker achter Transactie 1 onvoldoende saldo op zijn rekening heeft staan, wordt Transactie 1 gerold terug en de waarde voor de kolom ItemsInStock keert terug naar 12.
Transactie 2 heeft echter 11 als waarde voor de kolom ItemsInStock. Dit zijn vuile gegevens en het probleem wordt een vuil leesprobleem genoemd.
Werkend voorbeeld van een vuil leesprobleem
Laten we eens kijken naar het vuile leesprobleem in actie in SQL Server. Laten we zoals altijd eerst onze tabel maken en er wat dummy-gegevens aan toevoegen. Voer het volgende script uit op uw databaseserver.
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)
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.
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
In het bovenstaande script starten we een nieuwe transactie die de waarde bijwerkt voor de kolom "ItemsInStock" van de productentabel waarbij Id 1 is. Vervolgens simuleren we de vertraging voor het factureren van de klant met behulp van de functies 'Wachten op' en 'Vertraging'. In het script is een vertraging van 10 seconden ingesteld. Daarna draaien we de transactie gewoon terug.
In het tweede geval van SSMS voegen we eenvoudig de volgende SELECT-instructie toe.
USE pos;
-- Transaction 2
SELECT * FROM products
WHERE Id = 1
Voer nu eerst de eerste transactie uit, d.w.z. voer het script uit in de eerste instantie van SSMS, en voer dan onmiddellijk het script uit in de tweede instantie van de SSMS.
U zult zien dat beide transacties gedurende 10 seconden worden uitgevoerd en daarna zult u zien dat de waarde voor de kolom 'ItemsInStock' voor het record met Id 1 nog steeds 12 is, zoals blijkt uit de tweede transactie. Hoewel de eerste transactie deze heeft bijgewerkt naar 11, 10 seconden heeft gewacht en vervolgens is teruggedraaid naar 12, is de waarde die wordt weergegeven door de tweede transactie 12 in plaats van 11.
Wat er feitelijk gebeurde, is dat toen we de eerste transactie uitvoerden, de waarde voor de kolom 'ItemsinStock' werd bijgewerkt. Het wachtte vervolgens 10 seconden en rolde de transactie vervolgens terug.
Hoewel we de tweede transactie onmiddellijk na de eerste begonnen, moest deze wachten tot de eerste transactie was voltooid. Daarom wachtte de tweede transactie ook 10 seconden en werd de tweede transactie uitgevoerd onmiddellijk nadat de eerste transactie de uitvoering had voltooid.
Lees vastgelegd isolatieniveau
Waarom moest transactie 2 wachten op de voltooiing van transactie 1 voordat deze werd uitgevoerd?
Het antwoord is dat het standaard isolatieniveau tussen transacties "read commit" is. Het isolatieniveau Read Committed zorgt ervoor dat gegevens alleen kunnen worden gelezen door een transactie als deze zich in de status Committed bevindt.
In ons voorbeeld heeft transactie 1 de gegevens bijgewerkt, maar deze zijn niet vastgelegd totdat deze werd teruggedraaid. Dit is de reden waarom transactie 2 moest wachten op transactie 1 om de gegevens vast te leggen of de transactie terug te draaien voordat de gegevens konden worden gelezen.
Nu, in praktische scenario's, hebben we vaak meerdere transacties tegelijkertijd op één database en willen we niet dat elke transactie op zijn beurt moet wachten. Dit kan databases erg traag maken. Stel je voor dat je iets online koopt van een grote website die maar één transactie tegelijk kan verwerken!
Niet-vastgelegde gegevens lezen
Het antwoord op dit probleem is om uw transacties te laten werken met niet-vastgelegde gegevens.
Om niet-vastgelegde gegevens te lezen, stelt u eenvoudig het isolatieniveau van de transactie in op "niet-vastgelegde gegevens lezen". Werk transactie 2 bij door een isolatieniveau toe te voegen volgens het onderstaande script.
USE pos;
-- Transaction 2
set transaction isolation level read uncommitted
SELECT * FROM products
WHERE Id = 1
Als u nu transactie 1 uitvoert en vervolgens onmiddellijk transactie 2 uitvoert, zult u zien dat transactie 2 niet wacht op transactie 1 om gegevens vast te leggen. Transactie 2 zal onmiddellijk de vuile gegevens lezen. Dit wordt weergegeven in de volgende afbeelding:
Hier voert de instantie aan de linkerkant transactie 1 uit en de instantie aan de rechterkant transactie 2.
We voeren eerst transactie 1 uit, die de waarde van "ItemsinStock" voor id 1 naar 11 bijwerkt van 12 en vervolgens 10 seconden wacht voordat het wordt teruggedraaid.
Ondertussen leest transactie w de vuile gegevens die 11 zijn, zoals weergegeven in het resultatenvenster aan de rechterkant. Omdat transactie 1 wordt teruggedraaid, is dit niet de werkelijke waarde in de tabel. De werkelijke waarde is 12. Probeer transactie 2 opnieuw uit te voeren en u zult zien dat deze keer 12 wordt opgehaald.
Read uncommitted is het enige isolatieniveau met het vuile leesprobleem. Dit isolatieniveau is het minst beperkend van alle isolatieniveaus en maakt het mogelijk om niet-vastgelegde gegevens te lezen.
Het is duidelijk dat er voor- en nadelen zijn aan het gebruik van Read Uncommitted, het hangt af van voor welke toepassing uw database wordt gebruikt. Het zou natuurlijk een heel slecht idee zijn om dit te gebruiken voor de database achter een ATM-systeem en andere zeer veilige systemen. Voor toepassingen waar snelheid erg belangrijk is (het runnen van grote e-commerce winkels) is het echter logischer om Read Uncommitted te gebruiken.