sql >> Databasteknik >  >> RDS >> Database

Handledning för SQL-transaktioner

I SQL används transaktioner för att upprätthålla dataintegritet genom att säkerställa att en sekvens av SQL-satser körs helt eller inte alls.

Transaktioner hanterar sekvenser av SQL-satser som måste köras som en enda arbetsenhet, så att databasen aldrig innehåller resultaten av partiella operationer.

När en transaktion gör flera ändringar i databasen, lyckas antingen alla ändringar när transaktionen genomförs, eller så ångras alla ändringar när transaktionen återställs.

När ska man använda en transaktion?

Transaktioner är avgörande i situationer där dataintegriteten skulle vara i fara i händelse av att någon av en sekvens av SQL-satser skulle misslyckas.

Om du till exempel skulle flytta pengar från ett bankkonto till ett annat, skulle du behöva dra av pengar från ett konto och lägga till dem på det andra. Du vill inte att det ska misslyckas halvvägs, annars kan pengar debiteras från ett konto men inte krediteras det andra.

Möjliga orsaker till fel kan inkludera otillräckliga medel, ogiltigt kontonummer, ett maskinvarufel, etc.

Så du gör inte vill vara i en situation där det förblir så här:

Debit account 1 (Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)

Det skulle vara på riktigt dålig. Databasen skulle ha inkonsekventa data och pengar skulle försvinna ut i tomma intet. Då skulle banken förlora en kund (banken skulle förmodligen förlora alla sina kunder om detta fortsatte att hända), och du skulle förlora ditt jobb.

För att rädda ditt jobb kan du använda en transaktion som skulle se ut ungefär så här:

START TRANSACTION
Debit account 1
Credit account 2
Record transaction in transaction journal
END TRANSACTION 

Du kan skriva villkorlig logik inuti den transaktionen som rullar tillbaka transaktionen om något går fel.

Till exempel, om något går fel mellan debiteringskonto 1 och kreditering av konto 2, återställs hela transaktionen.

Därför skulle det bara finnas två möjliga utfall:

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)

Detta är en förenklad skildring, men det är en klassisk illustration av hur SQL-transaktioner fungerar. SQL-transaktioner har ACID.

Transaktionstyper

SQL-transaktioner kan köras i följande lägen.

Transaktionsläge Beskrivning
Autocommit transaktion Varje individuellt uttalande är en transaktion.
Implicit transaktion En ny transaktion startas implicit när den föregående transaktionen slutförs, men varje transaktion är explicit slutförd, vanligtvis med en COMMIT eller ROLLBACK uttalande beroende på DBMS.
Explicit transaktion Började explicit med en rad som START TRANSACTION , BEGIN TRANSACTION eller liknande, beroende på DBMS, och uttryckligen åtagit sig eller rullat tillbaka med relevanta uttalanden.
Batch-omfattad transaktion Gäller endast för flera aktiva resultatuppsättningar (MARS). En explicit eller implicit transaktion som startar under en MARS-session blir en batch-omfattad transaktion.

De exakta lägen och alternativ som är tillgängliga kan bero på DBMS. Den här tabellen beskriver de transaktionslägen som är tillgängliga i SQL Server.

I den här artikeln fokuserar vi huvudsakligen på explicita transaktioner.

Se Hur implicita transaktioner fungerar i SQL Server för en diskussion om skillnaden mellan implicita transaktioner och autocommit.

Sytnax

Följande tabell beskriver den grundläggande syntaxen för att starta och avsluta en explicit transaktion i några av de mer populära DBMS:erna.

DBMS Explicit transaktionssyntax
MySQL, MariaDB, PostgreSQL Explicita transaktioner börjar med START TRANSACTION eller BEGIN påstående. COMMIT begår den aktuella transaktionen, vilket gör dess ändringar permanenta. ROLLBACK återställer den aktuella transaktionen och avbryter dess ändringar.
SQLite Explicita transaktioner börjar med BEGIN TRANSACTION och avsluta med COMMIT eller ROLLBACK påstående. Kan också avslutas med END uttalande.
SQL-server Explicita transaktioner börjar med BEGIN TRANSACTION och avsluta med COMMIT eller ROLLBACK uttalande.
Oracle Explicita transaktioner börjar med SET TRANSACTION och avsluta med COMMIT eller ROLLBACK uttalande.

I många fall är vissa nyckelord valfria när du använder explicita transaktioner. Till exempel i SQL Server och SQLite kan du helt enkelt använda BEGIN (istället för BEGIN TRANSACTION ) och/eller du kan avsluta med COMMIT TRANSACTION (i motsats till bara COMMIT ).

Det finns också flera andra nyckelord och alternativ som du kan ange när du skapar en transaktion, så se din DBMS-dokumentation för hela syntaxen.

SQL-transaktionsexempel

Här är ett exempel på en enkel transaktion i SQL Server:

BEGIN TRANSACTION
    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION;

I detta fall raderas orderinformation från två tabeller. Båda påståendena behandlas som en enhet.

Vi skulle kunna skriva villkorlig logik i vår transaktion för att göra den återställd i händelse av ett fel.

Ge en transaktion ett namn

Vissa DBMS tillåter dig att ange ett namn för dina transaktioner. I SQL Server kan du lägga till ditt valda namn efter BEGIN och COMMIT uttalanden.

BEGIN TRANSACTION MyTransaction
    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION MyTransaction;

Exempel 1 för återställning av SQL-transaktioner

Här är det tidigare exemplet igen, men med lite extra kod. Den extra koden används för att återställa transaktionen i händelse av ett fel.:

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 uttalandet implementerar felhantering i SQL Server. Du kan innesluta vilken grupp som helst av T-SQL-satser i en TRY blockera. Sedan, om ett fel uppstår i TRY block, kontrollen skickas till en annan grupp av satser som är innesluten i en CATCH blockera.

I det här fallet använder vi CATCH blockera för att återställa transaktionen. Med tanke på att det finns i CATCH blockera, återställning sker endast om det finns ett fel.

Exempel 2 för återställning av SQL-transaktioner

Låt oss ta en närmare titt på databasen som vi just tog bort rader från.

I det föregående exemplet tog vi bort rader från Orders och OrderItems tabeller i följande databas:

I denna databas, varje gång en kund gör en beställning, infogas en rad i Orders tabell och en eller flera rader i OrderItems tabell. Antalet rader som infogats i OrderItems beror på hur många olika produkter kunden beställer.

Om det är en ny kund infogas en ny rad i Customers bord.

I så fall måste rader infogas i tre tabeller.

I händelse av ett misslyckande skulle vi inte vilja att en rad infogas i Orders tabell men inga motsvarande rader i OrderItems tabell. Det skulle resultera i en beställning utan några beställningsartiklar. I grund och botten vill vi att båda tabellerna ska vara helt uppdaterade eller inget alls.

Det var samma sak när vi tog bort raderna. Vi ville att alla rader skulle raderas eller inga alls.

I SQL Server kan vi skriva följande transaktion för INSERT uttalanden.

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

Det här exemplet förutsätter att det finns logik någon annanstans som avgör om kunden redan finns i databasen eller inte.

Kunden kunde ha infogats utanför denna 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

Om transaktionen misslyckades, skulle kunden fortfarande finnas i databasen (men utan några beställningar). Applikationen skulle behöva kontrollera om kunden redan finns innan transaktionen genomförs.

SQL-transaktion med räddningspunkter

En räddningspunkt definierar en plats till vilken en transaktion kan återvända om en del av transaktionen avbryts villkorligt. I SQL Server anger vi en räddningspunkt med SAVE TRANSACTION savepoint_name (där savepoint_name är namnet vi ger till räddningspunkten).

Låt oss skriva om det föregående exemplet för att inkludera en räddningspunkt:


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;

Här har vi satt en räddningspunkt direkt efter kundens INSERT påstående. Senare i transaktionen använder jag ROLLBACK uttalande för att instruera transaktionen att återställa till den sparade punkten.

När jag kör det uttalandet infogas kunden, men ingen av orderinformationen infogas.

Om en transaktion rullas tillbaka till en räddningspunkt måste den fortsätta till slutförandet med fler SQL-satser om det behövs och en COMMIT TRANSACTION uttalande, eller så måste det annulleras helt genom att återställa hela transaktionen.

Om jag flyttar ROLLBACK sats tillbaka till föregående INSERT uttalande, så här:

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;

Detta ger ett konfliktfel med främmande nyckel. Specifikt får jag följande felmeddelande:

(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)

Detta inträffade eftersom, även om ordern redan hade infogats, den operationen ångrades när vi rullade tillbaka till räddningspunkten. Sedan fortsatte transaktionen att slutföras. Men när den stötte på den slutliga beställningsvaran fanns det ingen motsvarande beställning (eftersom den hade ångrats), och vi fick felet.

När jag kontrollerade databasen infogades kunden, men återigen, ingen av orderinformationen infogades.

Du kan referera till samma sparpunkt från flera ställen i transaktionen om det behövs.

I praktiken skulle du använda villkorad programmering för att återföra transaktionen till en savepont.

Inkapslade transaktioner

Du kan också kapsla transaktioner i andra transaktioner om det behövs.

Så här:

BEGIN TRANSACTION Transaction1;  
    UPDATE table1 ...;
    BEGIN TRANSACTION Transaction2;
        UPDATE table2 ...;
        SELECT * from table1;
    COMMIT TRANSACTION Transaction2;
    UPDATE table3 ...;
COMMIT TRANSACTION Transaction1;

Som nämnts kommer den exakta syntaxen du använder för att skapa en transaktion att bero på ditt DBMS, så kontrollera din DBMS:s dokumentation för en fullständig bild av dina alternativ när du skapar transaktioner i SQL.


  1. MySQL PÅ DUBLIKATNYCKELUPPDATERING för flera rader infoga i en enda fråga

  2. PostgreSQL hur man ser vilka frågor som har körts

  3. PHP, ORM, MSSQL och Unicode, är det möjligt att få dessa att fungera tillsammans?

  4. olöst referens till objektet [INFORMATION_SCHEMA].[TABLER]