Ett av de vanligaste problemen som uppstår när du kör samtidiga transaktioner är Dirty Read-problemet. En smutsig läsning inträffar när en transaktion tillåts läsa data som modifieras av en annan transaktion som körs samtidigt men som ännu inte har begåtts.
Om transaktionen som ändrar data begår sig själv, uppstår inte det smutsiga läsproblemet. Men om transaktionen som ändrar data rullas tillbaka efter att den andra transaktionen har läst data, har den senare transaktionen smutsiga data som faktiskt inte existerar.
Som alltid, se till att du är väl säkerhetskopierad innan du experimenterar med en ny kod. Se den här artikeln om säkerhetskopiering av MS SQL-databaser om du är osäker.
Låt oss förstå detta med hjälp av ett exempel. Anta att vi har en tabell som heter 'Produkt' som lagrar id, namn och artiklar i lager för produkten.
Tabellen ser ut så här:
[tabell id=20 /]
Anta att du har ett onlinesystem där en användare kan köpa produkter och se produkter samtidigt. Ta en titt på följande bild.
Tänk på ett scenario där en användare försöker köpa en produkt. Transaktion 1 kommer att utföra köpuppgiften för användaren. Det första steget i transaktionen kommer att vara att uppdatera ItemsinStock.
Innan transaktionen finns det 12 artiklar i lager; transaktionen kommer att uppdatera detta till 11. Transaktionen kommer nu att kommunicera med en extern faktureringsgateway.
Om en annan transaktion, låt oss säga Transaktion 2, vid denna tidpunkt lyder ItemsInStock för bärbara datorer, kommer den att läsa 11. Men om användaren bakom Transaktion 1 senare visar sig ha otillräckliga medel på sitt konto, kommer Transaktion 1 att rullas tillbaka och värdet för kolumnen ItemsInStock kommer att återgå till 12.
Transaktion 2 har dock 11 som värde för kolumnen ItemsInStock. Detta är smutsiga data och problemet kallas dirty read problem.
Fungerande exempel på smutsavläsningsproblem
Låt oss ta en titt på det smutsiga läsproblemet i aktion i SQL Server. Som alltid, låt oss först skapa vår tabell och lägga till lite dummydata till den. Kör följande skript på din databasserver.
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)
Öppna nu två SQL Server Management Studio-instanser sida vid sida. Vi kommer att köra en transaktion i vart och ett av dessa fall.
Lägg till följande skript till den första instansen av 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
I skriptet ovan startar vi en ny transaktion som uppdaterar värdet för kolumnen "ItemsInStock" i produkttabellen där Id är 1. Vi simulerar sedan fördröjningen för fakturering av kunden genom att använda funktionerna 'Vänta på' och 'Fördröjning'. En fördröjning på 10 sekunder har ställts in i manuset. Efter det återställer vi helt enkelt transaktionen.
I den andra instansen av SSMS lägger vi helt enkelt till följande SELECT-sats.
USE pos;
-- Transaction 2
SELECT * FROM products
WHERE Id = 1
Kör nu först den första transaktionen, dvs kör skriptet i den första instansen av SSMS, och kör sedan omedelbart skriptet i den andra instansen av SSMS.
Du kommer att se att båda transaktionerna fortsätter att utföras i 10 sekunder och efter det kommer du att se att värdet för kolumnen 'ItemsInStock' för posten med Id 1 fortfarande är 12 som visas av den andra transaktionen. Även om den första transaktionen uppdaterade den till 11, väntade i 10 sekunder och sedan rullade tillbaka den till 12, är värdet som visas av den andra transaktionen 12 istället för 11.
Vad som faktiskt hände är att när vi körde den första transaktionen uppdaterade den värdet för kolumnen 'ItemsinStock'. Den väntade sedan i 10 sekunder och återställde sedan transaktionen.
Även om vi startade den andra transaktionen direkt efter den första, fick den vänta på att den första transaktionen skulle slutföras. Det är därför den andra transaktionen också väntade i 10 sekunder och varför den andra transaktionen utfördes omedelbart efter att den första transaktionen slutförts.
Läs engagerad isoleringsnivå
Varför behövde transaktion 2 vänta på att transaktion 1 slutfördes innan den utfördes?
Svaret är att standardisoleringsnivån mellan transaktioner är "read committed". Isoleringsnivån Read Committed säkerställer att data endast kan läsas av en transaktion om den är i det engagerade tillståndet.
I vårt exempel uppdaterade transaktion 1 informationen men den gjorde det inte förrän den återställdes. Det är därför transaktion 2 var tvungen att vänta på transaktion 1 för att överföra data eller återställa transaktionen innan den kunde läsa data.
Nu, i praktiska scenarier, har vi ofta flera transaktioner som äger rum på en enda databas samtidigt och vi vill inte att varje transaktion ska behöva vänta på sin tur. Detta kan göra databaser mycket långsamma. Föreställ dig att köpa något online från en stor webbplats som bara kunde bearbeta en transaktion åt gången!
Läser oengagerad data
Svaret på det här problemet är att låta dina transaktioner fungera med oengagerad data.
För att läsa oengagerad data, ställ helt enkelt in isoleringsnivån för transaktionen till "läs oengagerad". Uppdatera transaktion 2 genom att lägga till en isoleringsnivå enligt skriptet nedan.
USE pos;
-- Transaction 2
set transaction isolation level read uncommitted
SELECT * FROM products
WHERE Id = 1
Om du nu kör transaktion 1 och sedan omedelbart kör transaktion 2, kommer du att se att transaktion 2 inte väntar på att transaktion 1 ska överföra data. Transaktion 2 kommer omedelbart att läsa den smutsiga datan. Detta visas i följande bild:
Här kör instansen till vänster transaktion 1 och instansen till höger kör transaktion 2.
Vi kör först transaktion 1 som uppdaterar värdet på "ItemsinStock" för id 1 till 11 från 12 och väntar sedan i 10 sekunder innan den rullas tillbaka.
Under tiden läser transaktion w den smutsiga datan som är 11, som visas i resultatfönstret till höger. Eftersom transaktion 1 återställs är detta inte det faktiska värdet i tabellen. Det faktiska värdet är 12. Försök att utföra transaktion 2 igen och du kommer att se att den här gången hämtar 12.
Läs oengagerad är den enda isoleringsnivån som har problemet med smutsig läsning. Denna isoleringsnivå är minst restriktiv av alla isoleringsnivåer och tillåter läsning av oengagerad data.
Uppenbarligen finns det för- och nackdelar med att använda Read Uncommitted det beror på vilket program din databas används för. Uppenbarligen skulle det vara en mycket dålig idé att använda detta för databasen bakom ett ATM-system och andra mycket säkra system. Men för applikationer där hastigheten är mycket viktig (att driva stora e-handelsbutiker) är det mer meningsfullt att använda Read Uncommitted.