sql >> Databáze >  >> RDS >> Database

Kurz transakcí SQL

V SQL se transakce používají k udržení integrity dat zajištěním toho, že sekvence příkazů SQL se provede úplně nebo vůbec.

Transactions spravují sekvence SQL příkazů, které musí být provedeny jako jedna jednotka práce, takže databáze nikdy neobsahuje výsledky dílčích operací.

Když transakce provede více změn v databázi, buď budou všechny změny úspěšné, když je transakce potvrzena, nebo se všechny změny vrátí zpět, když je transakce odvolána.

Kdy použít transakci?

Transakce jsou prvořadé v situacích, kdy by byla ohrožena integrita dat v případě, že by některý z posloupnosti příkazů SQL selhal.

Pokud byste například přesouvali peníze z jednoho bankovního účtu na druhý, museli byste peníze odečíst z jednoho účtu a přidat je na druhý. Nechtěli byste, aby selhal v polovině, jinak by mohly být peníze odepsány z jednoho účtu, ale nebyly připsány na druhý.

Možné důvody selhání mohou zahrnovat nedostatek finančních prostředků, neplatné číslo účtu, selhání hardwaru atd.

Takže ne chci být v situaci, kdy to zůstane takto:

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

To by bylo skutečně špatný. Databáze by měla nekonzistentní data a peníze by zmizely ve vzduchu. Pak by banka přišla o zákazníka (banka by pravděpodobně přišla o všechny své zákazníky, kdyby se to opakovalo) a vy byste přišli o práci.

Chcete-li uložit svou práci, můžete použít transakci, která by vypadala nějak takto:

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

Do této transakce můžete napsat podmíněnou logiku, která transakci vrátí zpět, pokud se něco pokazí.

Pokud se například něco pokazí mezi odepsáním účtu 1 a připsáním na účet 2, celá transakce bude vrácena zpět.

Proto by byly pouze dva možné výsledky:

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

Nebo:

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

Toto je zjednodušené zobrazení, ale je to klasická ilustrace toho, jak fungují transakce SQL. SQL transakce mají ACID.

Typy transakcí

Transakce SQL lze spouštět v následujících režimech.

Režim transakce Popis
Automatické potvrzení transakce Každý jednotlivý výpis je transakce.
Implicitní transakce Nová transakce je implicitně zahájena po dokončení předchozí transakce, ale každá transakce je dokončena explicitně, obvykle s COMMIT nebo ROLLBACK v závislosti na DBMS.
Explicitní transakce Explicitně začíná řádkem, jako je START TRANSACTION , BEGIN TRANSACTION nebo podobné, v závislosti na DBMS, a explicitně potvrzené nebo vrácené zpět s příslušnými prohlášeními.
Transakce v dávkovém rozsahu Platí pouze pro více aktivních sad výsledků (MARS). Explicitní nebo implicitní transakce, která začíná v rámci relace MARS, se stává transakcí s dávkovým rozsahem.

Přesné dostupné režimy a možnosti mohou záviset na DBMS. Tato tabulka popisuje režimy transakcí dostupné na serveru SQL.

V tomto článku se zaměřujeme hlavně na explicitní transakce.

Podívejte se, jak fungují implicitní transakce v SQL Server, kde najdete diskuzi o rozdílu mezi implicitními transakcemi a automatickým potvrzením.

Sytnax

Následující tabulka uvádí základní syntaxi pro zahájení a ukončení explicitní transakce v některých populárnějších DBMS.

DBMS Explicitní syntaxe transakce
MySQL, MariaDB, PostgreSQL Explicitní transakce začínají START TRANSACTION nebo BEGIN prohlášení. COMMIT potvrdí aktuální transakci a její změny budou trvalé. ROLLBACK vrátí aktuální transakci zpět a zruší její změny.
SQLite Explicitní transakce začínají BEGIN TRANSACTION a končí COMMIT nebo ROLLBACK prohlášení. Může také končit END prohlášení.
SQL Server Explicitní transakce začínají BEGIN TRANSACTION a končí COMMIT nebo ROLLBACK prohlášení.
Oracle Explicitní transakce začínají SET TRANSACTION a končí COMMIT nebo ROLLBACK prohlášení.

V mnoha případech jsou určitá klíčová slova při použití explicitních transakcí volitelná. Například v SQL Server a SQLite můžete jednoduše použít BEGIN (spíše než BEGIN TRANSACTION ) a/nebo můžete skončit COMMIT TRANSACTION (na rozdíl od pouhého COMMIT ).

Existují také různá další klíčová slova a možnosti, které můžete zadat při vytváření transakce, takže úplnou syntaxi najdete v dokumentaci k DBMS.

Příklad transakce SQL

Zde je příklad jednoduché transakce v SQL Server:

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

V tomto případě jsou informace o objednávce odstraněny ze dvou tabulek. Oba příkazy jsou považovány za jednu jednotku práce.

Do naší transakce bychom mohli zapsat podmíněnou logiku, abychom ji v případě chyby vrátili zpět.

Pojmenování transakce

Některé DBMS umožňují zadat název pro vaše transakce. V SQL Server můžete přidat vámi zvolené jméno za BEGIN a COMMIT prohlášení.

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

Příklad vrácení transakce SQL 1

Zde je opět předchozí příklad, ale s nějakým kódem navíc. Extra kód se používá k vrácení transakce v případě chyby.:

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 implementuje zpracování chyb v SQL Server. Libovolnou skupinu příkazů T-SQL můžete uzavřít do TRY blok. Pak, pokud dojde k chybě v TRY bloku, je řízení předáno jiné skupině příkazů, která je uzavřena v CATCH blokovat.

V tomto případě použijeme CATCH blokovat pro vrácení transakce. Vzhledem k tomu, že je to v CATCH blokovat, k vrácení dojde pouze v případě, že dojde k chybě.

Příklad vrácení transakce SQL 2

Podívejme se blíže na databázi, ze které jsme právě smazali řádky.

V předchozím příkladu jsme odstranili řádky z Orders a OrderItems tabulky v následující databázi:

V této databázi se pokaždé, když zákazník zadá objednávku, vloží řádek do Orders tabulky a jeden nebo více řádků do OrderItems stůl. Počet řádků vložených do OrderItems závisí na tom, kolik různých produktů si zákazník objedná.

Pokud se jedná o nového zákazníka, vloží se nový řádek do Customers tabulka.

V takovém případě je třeba vložit řádky do tří tabulek.

V případě selhání bychom nechtěli vkládat řádek do Orders tabulka, ale žádné odpovídající řádky v OrderItems stůl. Výsledkem by byla objednávka bez položek objednávky. V zásadě chceme, aby byly obě tabulky kompletně aktualizovány nebo vůbec nic.

Bylo to stejné, když jsme smazali řádky. Chtěli jsme odstranit všechny řádky nebo vůbec žádné.

V SQL Server bychom mohli napsat následující transakci pro INSERT prohlášení.

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

Tento příklad předpokládá, že jinde existuje logika, která určuje, zda zákazník již v databázi existuje, či nikoli.

Zákazník mohl být vložen mimo tuto transakci:


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

Pokud by se transakce nezdařila, zákazník by byl stále v databázi (ale bez jakýchkoli objednávek). Aplikace by musela před provedením transakce zkontrolovat, zda zákazník již existuje.

Transakce SQL s body uložení

Bod uložení definuje místo, kam se může transakce vrátit, pokud je část transakce podmíněně zrušena. Na serveru SQL Server určujeme bod uložení pomocí SAVE TRANSACTION savepoint_name (kde název_bodu je jméno, které dáváme bodu uložení).

Přepišme předchozí příklad tak, aby obsahoval bod uložení:


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;

Zde jsme nastavili bod uložení přímo za zákazníkem INSERT prohlášení. Později v transakci používám ROLLBACK příkaz, který dá transakci pokyn vrátit se zpět k tomuto bodu uložení.

Když spustím tento příkaz, zákazník je vložen, ale nejsou vloženy žádné informace o objednávce.

Pokud je transakce vrácena zpět do bodu uložení, musí v případě potřeby pokračovat v dokončení pomocí dalších příkazů SQL a COMMIT TRANSACTION nebo musí být úplně zrušeno vrácením celé transakce.

Pokud přesunu ROLLBACK příkaz zpět na předchozí INSERT prohlášení, jako je toto:

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;

To způsobí chybu konfliktu cizího klíče. Konkrétně se mi zobrazuje následující chyba:

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

Došlo k tomu proto, že přestože objednávka již byla vložena, tato operace byla vrácena zpět, když jsme se vrátili zpět k bodu uložení. Poté transakce pokračovala do konce. Když však narazila na položku konečné objednávky, neexistovala žádná odpovídající objednávka (protože byla vrácena zpět) a došlo k chybě.

Když jsem zkontroloval databázi, zákazník byl vložen, ale opět nebyly vloženy žádné informace o objednávce.

V případě potřeby můžete odkazovat na stejný bod uložení z více míst v transakci.

V praxi byste použili podmíněné programování k vrácení transakce na savepont.

Vnořené transakce

V případě potřeby můžete také vnořit transakce do jiných transakcí.

Takhle:

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

Jak již bylo zmíněno, přesná syntaxe, kterou použijete k vytvoření transakce, bude záviset na vaší DBMS, takže si v dokumentaci DBMS vyhledejte úplný obrázek o vašich možnostech při vytváření transakcí v SQL.


  1. Jak mohu opravit chybu MySQL # 1064?

  2. Provedení příkazu MySQL trvá déle než minutu

  3. Spusťte Oracle Forms jako samostatný bez prohlížeče

  4. Jak získat informace o chybě kompilace v Oracle/TOAD