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.