Et af de mest almindelige problemer, der opstår, mens du kører samtidige transaktioner, er Dirty Read-problemet. En beskidt læsning opstår, når en transaktion har tilladelse til at læse data, der bliver ændret af en anden transaktion, der kører samtidigt, men som endnu ikke har forpligtet sig.
Hvis transaktionen, der ændrer dataene, forpligter sig selv, opstår det beskidte læseproblem ikke. Men hvis transaktionen, der ændrer dataene, rulles tilbage, efter at den anden transaktion har læst dataene, har sidstnævnte transaktion beskidte data, som faktisk ikke eksisterer.
Som altid skal du sikre dig, at du er godt sikkerhedskopieret, før du eksperimenterer med en ny kode. Se denne artikel om sikkerhedskopiering af MS SQL-databaser, hvis du ikke er sikker.
Lad os forstå dette ved hjælp af et eksempel. Antag, at vi har en tabel med navnet 'Produkt', der gemmer id, navn og vare på lager for produktet.
Tabellen ser således ud:
[tabel id=20 /]
Antag, at du har et online system, hvor en bruger kan købe produkter og se produkter på samme tid. Tag et kig på følgende figur.
Overvej et scenario, hvor en bruger forsøger at købe et produkt. Transaktion 1 vil udføre købsopgaven for brugeren. Det første trin i transaktionen vil være at opdatere varerne på lageret.
Før handlen er der 12 varer på lageret; transaktionen vil opdatere denne til 11. Transaktionen vil nu kommunikere med en ekstern faktureringsgateway.
Hvis en anden transaktion, lad os sige Transaktion 2, på dette tidspunkt læser ItemsInStock til bærbare computere, vil den læse 11. Men hvis brugeren bag Transaktion 1 efterfølgende viser sig at have utilstrækkelige midler på sin konto, vil Transaktion 1 blive rullet tilbage, og værdien for kolonnen ItemsInStock vil vende tilbage til 12.
Transaktion 2 har dog 11 som værdi for kolonnen ItemsInStock. Dette er beskidte data, og problemet kaldes dirty read problem.
Fungerende eksempel på beskidt læseproblem
Lad os tage et kig på det beskidte læseproblem i aktion i SQL Server. Som altid, lad os først oprette vores tabel og tilføje nogle dummy-data til den. Udfør følgende script på din 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)
Åbn nu to SQL Server Management Studio-instanser side om side. Vi kører én transaktion i hvert af disse tilfælde.
Tilføj følgende script til den første forekomst af 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 ovenstående script starter vi en ny transaktion, der opdaterer værdien for "ItemsInStock"-kolonnen i produkttabellen, hvor Id er 1. Vi simulerer derefter forsinkelsen for fakturering af kunden ved at bruge funktionerne 'WaitFor' og 'Delay'. Der er indstillet en forsinkelse på 10 sekunder i scriptet. Derefter ruller vi blot transaktionen tilbage.
I den anden forekomst af SSMS tilføjer vi blot følgende SELECT-sætning.
USE pos;
-- Transaction 2
SELECT * FROM products
WHERE Id = 1
Kør nu først den første transaktion, dvs. eksekver scriptet i den første instans af SSMS, og kør derefter straks scriptet i den anden instans af SSMS.
Du vil se, at begge transaktioner fortsætter med at udføre i 10 sekunder, og derefter vil du se, at værdien for 'ItemsInStock'-kolonnen for posten med Id 1 stadig er 12, som vist ved den anden transaktion. Selvom den første transaktion opdaterede den til 11, ventede i 10 sekunder og derefter rullede den tilbage til 12, er værdien vist ved den anden transaktion 12 i stedet for 11.
Det, der faktisk skete, er, at da vi kørte den første transaktion, opdaterede den værdien for 'ItemsinStock'-kolonnen. Den ventede derefter i 10 sekunder og rullede derefter transaktionen tilbage.
Selvom vi startede den anden transaktion umiddelbart efter den første, måtte den vente på, at den første transaktion blev fuldført. Det er grunden til, at den anden transaktion også ventede i 10 sekunder, og hvorfor den anden transaktion blev udført umiddelbart efter, at den første transaktion var fuldført.
Læs forpligtet isolationsniveau
Hvorfor skulle transaktion 2 vente på fuldførelsen af transaktion 1, før den blev udført?
Svaret er, at standard isolationsniveauet mellem transaktioner er "læs committed". Read Committed-isolationsniveauet sikrer, at data kun kan læses af en transaktion, hvis den er i den forpligtede tilstand.
I vores eksempel opdaterede transaktion 1 dataene, men den forpligtede dem ikke, før de blev rullet tilbage. Dette er grunden til, at transaktion 2 måtte vente på, at transaktion 1 kunne begå dataene eller rulle transaktionen tilbage, før den kunne læse dataene.
Nu, i praktiske scenarier, har vi ofte flere transaktioner, der finder sted på en enkelt database på samme tid, og vi ønsker ikke, at hver transaktion skal vente på sin tur. Dette kan gøre databaser meget langsomme. Forestil dig at købe noget online fra et stort websted, som kun kunne behandle én transaktion ad gangen!
Læsning af uforpligtende data
Svaret på dette problem er at tillade dine transaktioner at arbejde med uforpligtende data.
For at læse ikke-forpligtede data skal du blot indstille isolationsniveauet for transaktionen til "læs ikke-forpligtet". Opdater transaktion 2 ved at tilføje et isolationsniveau i henhold til scriptet nedenfor.
USE pos;
-- Transaction 2
set transaction isolation level read uncommitted
SELECT * FROM products
WHERE Id = 1
Hvis du nu kører transaktion 1 og derefter straks kører transaktion 2, vil du se, at transaktion 2 ikke vil vente på, at transaktion 1 begår data. Transaktion 2 vil straks læse de beskidte data. Dette er vist i følgende figur:
Her kører instansen til venstre transaktion 1, og instansen til højre kører transaktion 2.
Vi kører først transaktion 1, som opdaterer værdien af "ItemsinStock" for id 1 til 11 fra 12 og venter derefter i 10 sekunder, før den rulles tilbage.
I mellemtiden læser transaktion w de beskidte data, som er 11, som vist i resultatvinduet til højre. Fordi transaktion 1 er rullet tilbage, er dette ikke den faktiske værdi i tabellen. Den faktiske værdi er 12. Prøv at udføre transaktion 2 igen, og du vil se, at denne gang henter den 12.
Læs uforpligtende er det eneste isolationsniveau, der har problemet med beskidt læse. Dette isolationsniveau er mindst restriktivt for alle isolationsniveauer og tillader læsning af ikke-forpligtede data.
Der er naturligvis fordele og ulemper ved at bruge Read Uncommitted, det afhænger af hvilket program din database bruges til. Det ville naturligvis være en meget dårlig idé at bruge dette til databasen bag et ATM-system og andre meget sikre systemer. For applikationer, hvor hastighed er meget vigtig (drift af store e-handelsbutikker), giver det dog mere mening at bruge Read Uncommitted.