I SQL bruges transaktioner til at opretholde dataintegriteten ved at sikre, at en sekvens af SQL-sætninger udføres fuldstændigt eller slet ikke.
Transaktioner administrerer sekvenser af SQL-sætninger, der skal udføres som en enkelt arbejdsenhed, så databasen aldrig indeholder resultaterne af delvise operationer.
Når en transaktion foretager flere ændringer i databasen, lykkes enten alle ændringerne, når transaktionen er forpligtet, eller alle ændringerne fortrydes, når transaktionen rulles tilbage.
Hvornår skal en transaktion bruges?
Transaktioner er altafgørende i situationer, hvor dataintegriteten ville være i fare i tilfælde af, at en af en sekvens af SQL-sætninger skulle mislykkes.
Hvis du for eksempel flyttede penge fra en bankkonto til en anden, skulle du trække penge fra den ene konto og lægge dem til den anden. Du vil ikke have, at den fejler halvvejs, ellers kan penge blive debiteret fra den ene konto, men ikke krediteret den anden.
Mulige årsager til fejl kan omfatte utilstrækkelige midler, ugyldigt kontonummer, hardwarefejl osv.
Så det gør du ikke ønsker at være i en situation, hvor det forbliver sådan her:
Debit account 1 (Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)
Det ville være virkelig dårligt. Databasen ville have inkonsistente data, og penge ville forsvinde ud i den blå luft. Så ville banken miste en kunde (banken ville sandsynligvis miste alle sine kunder, hvis dette blev ved med at ske), og du ville miste dit job.
For at redde dit job kan du bruge en transaktion, der ville se sådan ud:
START TRANSACTION
Debit account 1
Credit account 2
Record transaction in transaction journal
END TRANSACTION
Du kan skrive betinget logik inde i den transaktion, der ruller transaktionen tilbage, hvis noget går galt.
For eksempel, hvis noget går galt mellem debitering af konto 1 og kreditering af konto 2, rulles hele transaktionen tilbage.
Derfor ville der kun være to mulige udfald:
Debit account 1 (Not Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)
Eller:
Debit account 1 (Done)
Credit account 2 (Done)
Record transaction in transaction journal (Done)
Dette er en forenklet skildring, men det er en klassisk illustration af, hvordan SQL-transaktioner fungerer. SQL-transaktioner har ACID.
Transaktionstyper
SQL-transaktioner kan køres i følgende tilstande.
Transaktionstilstand | Beskrivelse |
---|---|
Autocommit transaktion | Hvert individuelt udsagn er en transaktion. |
Implicit transaktion | En ny transaktion startes implicit, når den foregående transaktion er fuldført, men hver transaktion afsluttes eksplicit, typisk med en COMMIT eller ROLLBACK sætning afhængigt af DBMS. |
Eksplicit transaktion | Begyndte eksplicit med en linje såsom START TRANSACTION , BEGIN TRANSACTION eller lignende, afhængigt af DBMS, og eksplicit forpligtet eller rullet tilbage med de relevante udsagn. |
Batch-omfattet transaktion | Gælder kun for flere aktive resultatsæt (MARS). En eksplicit eller implicit transaktion, der starter under en MARS-session, bliver en batch-omfattet transaktion. |
De nøjagtige tilgængelige tilstande og muligheder kan afhænge af DBMS. Denne tabel viser de tilgængelige transaktionstilstande i SQL Server.
I denne artikel er vi hovedsageligt fokuseret på eksplicitte transaktioner.
Se, hvordan implicitte transaktioner fungerer i SQL Server for en diskussion af forskellen mellem implicitte transaktioner og autocommit.
Sytnax
Følgende tabel skitserer den grundlæggende syntaks for at starte og afslutte en eksplicit transaktion i nogle af de mere populære DBMS'er.
DBMS | Eksplicit transaktionssyntaks |
---|---|
MySQL, MariaDB, PostgreSQL | Eksplicitte transaktioner starter med START TRANSACTION eller BEGIN udmelding. COMMIT begår den aktuelle transaktion, hvilket gør dens ændringer permanente. ROLLBACK ruller den aktuelle transaktion tilbage og annullerer dens ændringer. |
SQLite | Eksplicitte transaktioner starter med BEGIN TRANSACTION sætning og afslut med COMMIT eller ROLLBACK udmelding. Kan også slutte med END erklæring. |
SQL-server | Eksplicitte transaktioner starter med BEGIN TRANSACTION sætning og afslut med COMMIT eller ROLLBACK erklæring. |
Oracle | Eksplicitte transaktioner starter med SET TRANSACTION sætning og afslut med COMMIT eller ROLLBACK erklæring. |
I mange tilfælde er visse søgeord valgfrie, når der bruges eksplicitte transaktioner. For eksempel i SQL Server og SQLite kan du blot bruge BEGIN
(i stedet for BEGIN TRANSACTION
) og/eller du kan afslutte med COMMIT TRANSACTION
(i modsætning til bare COMMIT
).
Der er også forskellige andre nøgleord og muligheder, som du kan angive, når du opretter en transaktion, så se din DBMS's dokumentation for den fulde syntaks.
SQL-transaktionseksempel
Her er et eksempel på en simpel transaktion i SQL Server:
BEGIN TRANSACTION
DELETE OrderItems WHERE OrderId = 5006;
DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION;
I dette tilfælde slettes ordreoplysninger fra to tabeller. Begge udsagn behandles som én arbejdsenhed.
Vi kunne skrive betinget logik ind i vores transaktion for at få den til at rulle tilbage i tilfælde af en fejl.
Navngivning af en transaktion
Nogle DBMS'er giver dig mulighed for at angive et navn til dine transaktioner. I SQL Server kan du tilføje dit valgte navn efter BEGIN
og COMMIT
udsagn.
BEGIN TRANSACTION MyTransaction
DELETE OrderItems WHERE OrderId = 5006;
DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION MyTransaction;
Eksempel 1 på tilbagerulning af SQL-transaktioner
Her er det forrige eksempel igen, men med noget ekstra kode. Den ekstra kode bruges til at rulle tilbage transaktionen i tilfælde af en fejl.:
BEGIN TRANSACTION MyTransaction
BEGIN TRY
DELETE OrderItems WHERE OrderId = 5006;
DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION MyTransaction
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION MyTransaction
END CATCH
TRY...CATCH
sætning implementerer fejlhåndtering i SQL Server. Du kan omslutte enhver gruppe af T-SQL-sætninger i en TRY
blok. Derefter, hvis der opstår en fejl i TRY
blok, overføres kontrol til en anden gruppe af udsagn, der er indesluttet i en CATCH
blokere.
I dette tilfælde bruger vi CATCH
blokere for at rulle transaktionen tilbage. Fordi det er i CATCH
blokerer, tilbagerulning sker kun, hvis der er en fejl.
Eksempel 2 på tilbagerulning af SQL-transaktioner
Lad os se nærmere på den database, vi lige har slettet rækker fra.
I det foregående eksempel slettede vi rækker fra Orders
og OrderItems
tabeller i følgende database:
I denne database, hver gang en kunde afgiver en ordre, indsættes en række i Orders
tabel og en eller flere rækker i OrderItems
bord. Antallet af rækker, der er indsat i OrderItems
afhænger af, hvor mange forskellige produkter kunden bestiller.
Hvis det er en ny kunde, indsættes en ny række i Customers
tabel.
I så fald skal rækker indsættes i tre tabeller.
I tilfælde af en fejl ønsker vi ikke at have en række indsat i Orders
tabel, men ingen tilsvarende rækker i OrderItems
bord. Det ville resultere i en ordre uden ordrevarer. Grundlæggende ønsker vi, at begge tabeller skal være fuldstændig opdaterede eller slet ikke noget.
Det var det samme, da vi slettede rækkerne. Vi ville have alle rækker slettet eller slet ingen.
I SQL Server kunne vi skrive følgende transaktion for INSERT
udsagn.
BEGIN TRANSACTION
BEGIN TRY
INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');
INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
VALUES ( 5006, SYSDATETIME(), 1006 );
INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
VALUES ( 5006, 1, 1, 20, 25.99 );
INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
VALUES ( 5006, 2, 7, 120, 9.99 );
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
Dette eksempel antager, at der er logik andre steder, der bestemmer, om kunden allerede eksisterer i databasen.
Kunden kunne være blevet indsat uden for denne transaktion:
INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');
BEGIN TRANSACTION
BEGIN TRY
INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
VALUES ( 5006, SYSDATETIME(), 1006 );
INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
VALUES ( 5006, 1, 1, 20, 25.99 );
INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
VALUES ( 5006, 2, 7, 120, 9.99 );
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
Hvis transaktionen mislykkedes, ville kunden stadig være i databasen (men uden nogen ordrer). Applikationen skal kontrollere, om kunden allerede eksisterer, før transaktionen udføres.
SQL-transaktion med sparepunkter
Et lagringspunkt definerer et sted, hvortil en transaktion kan vende tilbage, hvis en del af transaktionen er betinget annulleret. I SQL Server angiver vi et savepoint med SAVE TRANSACTION savepoint_name
(hvor savepoint_name er det navn, vi giver til lagringspunktet).
Lad os omskrive det forrige eksempel for at inkludere et lagringspunkt:
BEGIN TRANSACTION
INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');
SAVE TRANSACTION StartOrder;
INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
VALUES ( 5006, SYSDATETIME(), 1006 );
INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
VALUES ( 5006, 1, 1, 20, 25.99 );
INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
VALUES ( 5006, 2, 7, 120, 9.99 );
ROLLBACK TRANSACTION StartOrder;
COMMIT TRANSACTION;
SELECT @@TRANCOUNT;
Her har vi sat et sparepunkt lige efter kundens INSERT
udmelding. Senere i transaktionen bruger jeg ROLLBACK
sætning for at instruere transaktionen om at rulle tilbage til det lagringspunkt.
Når jeg kører den erklæring, indsættes kunden, men ingen af ordreoplysningerne er indsat.
Hvis en transaktion rulles tilbage til et lagringspunkt, skal den fortsætte til fuldførelse med flere SQL-sætninger, hvis det er nødvendigt og en COMMIT TRANSACTION
erklæring, eller den skal annulleres helt ved at rulle hele transaktionen tilbage.
Hvis jeg flytter ROLLBACK
sætning tilbage til den forrige INSERT
erklæring som denne:
BEGIN TRANSACTION
INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');
SAVE TRANSACTION StartOrder;
INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
VALUES ( 5006, SYSDATETIME(), 1006 );
INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
VALUES ( 5006, 1, 1, 20, 25.99 );
ROLLBACK TRANSACTION StartOrder;
INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
VALUES ( 5006, 2, 7, 120, 9.99 );
COMMIT TRANSACTION;
SELECT @@TRANCOUNT;
Dette producerer en fremmednøglekonfliktfejl. Helt konkret får jeg følgende fejl:
(1 row affected) (1 row affected) (1 row affected) Msg 547, Level 16, State 0, Line 13 The INSERT statement conflicted with the FOREIGN KEY constraint "FK_OrderItems_Orders". The conflict occurred in database "KrankyKranes", table "dbo.Orders", column 'OrderId'. The statement has been terminated. (1 row affected)
Dette skete, fordi, selvom ordren allerede var blevet indsat, blev den handling fortrydet, da vi rullede tilbage til lagringspunktet. Derefter fortsatte transaktionen til afslutning. Men da den stødte på den endelige ordre vare, var der ingen tilsvarende ordre (fordi den var blevet fortrudt), og vi fik fejlen.
Da jeg tjekkede databasen, blev kunden indsat, men igen, ingen af ordreoplysningerne blev indsat.
Du kan referere til det samme lagringspunkt fra flere steder i transaktionen, hvis det kræves.
I praksis ville du bruge betinget programmering til at returnere transaktionen til en savepont.
Indlejrede transaktioner
Du kan også indlejre transaktioner i andre transaktioner, hvis det kræves.
Sådan:
BEGIN TRANSACTION Transaction1;
UPDATE table1 ...;
BEGIN TRANSACTION Transaction2;
UPDATE table2 ...;
SELECT * from table1;
COMMIT TRANSACTION Transaction2;
UPDATE table3 ...;
COMMIT TRANSACTION Transaction1;
Som nævnt vil den nøjagtige syntaks, du bruger til at oprette en transaktion, afhænge af dit DBMS, så tjek dit DBMS’s dokumentation for et komplet billede af dine muligheder, når du opretter transaktioner i SQL.