В SQL транзакциите се използват за поддържане на целостта на данните, като се гарантира, че поредица от SQL изрази се изпълняват напълно или изобщо не се изпълняват.
Транзакциите управляват поредици от SQL изрази, които трябва да се изпълняват като една единица работа, така че базата данни никога да не съдържа резултатите от частични операции.
Когато транзакция направи множество промени в базата данни, или всички промени са успешни, когато транзакцията е ангажиментирана, или всички промени се отменят, когато транзакцията бъде връщана назад.
Кога да използвам транзакция?
Транзакциите са от първостепенно значение в ситуации, при които целостта на данните би била застрашена, в случай че някой от поредица от SQL изрази се провали.
Например, ако премествате пари от една банкова сметка в друга, ще трябва да приспаднете парите от една сметка и да ги добавите към другата. Не бихте искали да се провали по средата, в противен случай парите могат да бъдат дебитирани от една сметка, но не и кредитирани към другата.
Възможните причини за неуспех могат да включват недостатъчни средства, невалиден номер на сметката, повреда на хардуера и др.
Така че вие не искате да бъдете в ситуация, в която остава така:
Debit account 1 (Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)
Това би било наистина лошо. Базата данни ще има непоследователни данни и парите ще изчезнат във въздуха. Тогава банката ще загуби клиент (банката вероятно ще загуби всичките си клиенти, ако това продължи да се случва), а вие ще загубите работата си.
За да запазите работата си, можете да използвате транзакция, която би била нещо подобно:
START TRANSACTION
Debit account 1
Credit account 2
Record transaction in transaction journal
END TRANSACTION
Можете да напишете условна логика вътре в тази транзакция, която връща обратно транзакцията, ако нещо се обърка.
Например, ако нещо се обърка между дебитиране на сметка 1 и кредитиране на сметка 2, цялата транзакция се отменя.
Следователно би имало само два възможни резултата:
Debit account 1 (Not Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)
Или:
Debit account 1 (Done)
Credit account 2 (Done)
Record transaction in transaction journal (Done)
Това е опростено изображение, но е класическа илюстрация на това как работят SQL транзакциите. SQL транзакциите имат ACID.
Видове транзакции
SQL транзакциите могат да се изпълняват в следните режими.
Режим на транзакция | Описание |
---|---|
Автоматично извършване на транзакция | Всяко отделно изявление е транзакция. |
Неявна транзакция | Нова транзакция се стартира имплицитно, когато предишната транзакция завърши, но всяка транзакция е изрично завършена, обикновено с COMMIT или ROLLBACK изявление в зависимост от СУБД. |
Изрична транзакция | Изрично започва с ред като START TRANSACTION , BEGIN TRANSACTION или подобен, в зависимост от СУБД, и изрично ангажирани или върнати обратно със съответните оператори. |
Транзакция с обхват на партида | Приложимо само за множество активни набори от резултати (MARS). Изрична или неявна транзакция, която започва под сесия на MARS, става транзакция с обхват на партида. |
Точните налични режими и опции може да зависят от СУБД. Тази таблица очертава режимите на транзакции, налични в SQL Server.
В тази статия се фокусираме основно върху изрични транзакции.
Вижте как работят неявните транзакции в SQL Server за обсъждане на разликата между неявните транзакции и автоматичното извършване.
Sytnax
Следващата таблица очертава основния синтаксис за стартиране и завършване на изрична транзакция в някои от по-популярните СУБД.
DBMS | Явен синтаксис на транзакциите |
---|---|
MySQL, MariaDB, PostgreSQL | Изричните транзакции започват с START TRANSACTION или BEGIN изявление. COMMIT извършва текущата транзакция, като прави нейните промени постоянни. ROLLBACK отменя текущата транзакция, като отменя промените й. |
SQLite | Изричните транзакции започват с BEGIN TRANSACTION оператор и завършва с COMMIT или ROLLBACK изявление. Може също да завършва с END изявление. |
SQL сървър | Изричните транзакции започват с BEGIN TRANSACTION оператор и завършва с COMMIT или ROLLBACK изявление. |
Оракул | Изричните транзакции започват с SET TRANSACTION оператор и завършва с COMMIT или ROLLBACK изявление. |
В много случаи някои ключови думи са незадължителни, когато се използват изрични транзакции. Например в SQL Server и SQLite можете просто да използвате BEGIN
(вместо BEGIN TRANSACTION
) и/или бихте могли да завършите с COMMIT TRANSACTION
(за разлика от просто COMMIT
).
Има и различни други ключови думи и опции, които можете да посочите при създаване на транзакция, така че вижте документацията на вашата СУБД за пълния синтаксис.
Пример за SQL транзакция
Ето пример за проста транзакция в SQL Server:
BEGIN TRANSACTION
DELETE OrderItems WHERE OrderId = 5006;
DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION;
В този случай информацията за поръчката се изтрива от две таблици. И двете твърдения се третират като една единица работа.
Бихме могли да запишем условна логика в нашата транзакция, за да я върнем обратно в случай на грешка.
Именуване на транзакция
Някои СУБД ви позволяват да предоставите име за вашите транзакции. В SQL Server можете да добавите избраното от вас име след BEGIN
и COMMIT
изявления.
BEGIN TRANSACTION MyTransaction
DELETE OrderItems WHERE OrderId = 5006;
DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION MyTransaction;
Пример за връщане назад на SQL транзакция 1
Ето отново предишния пример, но с допълнителен код. Допълнителният код се използва за връщане на транзакцията в случай на грешка.:
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
оператор реализира обработка на грешки в SQL Server. Можете да затворите всяка група от T-SQL изрази в TRY
блок. След това, ако възникне грешка в TRY
блок, контролата се предава на друга група от изрази, която е затворена в CATCH
блок.
В този случай използваме CATCH
блокирайте за връщане на транзакцията. Като се има предвид, че е в CATCH
блок, връщане се извършва само ако има грешка.
Пример за връщане назад на SQL транзакция 2
Нека разгледаме по-отблизо базата данни, от която току-що изтрихме редове.
В предишния пример изтрихме редове от Orders
и OrderItems
таблици в следната база данни:
В тази база данни всеки път, когато клиент направи поръчка, се вмъква ред в Orders
таблица и един или повече реда в OrderItems
маса. Броят на редовете, вмъкнати в OrderItems
зависи от това колко различни продукти поръчва клиента.
Освен това, ако е нов клиент, нов ред се вмъква в Customers
таблица.
В този случай редовете трябва да бъдат вмъкнати в три таблици.
В случай на неуспех, не бихме искали да имаме ред, вмъкнат в Orders
таблица, но няма съответни редове в OrderItems
маса. Това ще доведе до поръчка без артикули за поръчка. По принцип искаме и двете таблици да бъдат напълно актуализирани или изобщо нищо.
Същото беше и когато изтрихме редовете. Искахме всички редове да бъдат изтрити или никакви.
В SQL Server бихме могли да напишем следната транзакция за INSERT
изявления.
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
Този пример предполага, че има логика другаде, която определя дали клиентът вече съществува или не в базата данни.
Клиентът може да е бил вмъкнат извън тази транзакция:
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
Ако транзакцията е неуспешна, клиентът все още ще бъде в базата данни (но без никакви поръчки). Приложението ще трябва да провери дали клиентът вече съществува, преди да извърши транзакцията.
SQL транзакция с точки за запис
Точката за запис дефинира местоположение, до което транзакцията може да се върне, ако част от транзакцията бъде условно отменена. В SQL Server ние указваме точка за запис с SAVE TRANSACTION savepoint_name
(където име_на_точка на запис е името, което даваме на точката за запис).
Нека пренапишем предишния пример, за да включим точка за запис:
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;
Тук сме задали точка за запис веднага след клиентския INSERT
изявление. По-късно в транзакцията използвам ROLLBACK
изявление, за да инструктира транзакцията да се върне към тази точка на запис.
Когато стартирам това изявление, клиентът се вмъква, но не се въвежда информация за поръчката.
Ако транзакция се връща обратно до точка на запис, тя трябва да продължи към завършване с повече SQL оператори, ако е необходимо и COMMIT TRANSACTION
изявление или трябва да бъде анулиран изцяло чрез отмяна на цялата транзакция.
Ако преместя ROLLBACK
изявление обратно към предишния INSERT
изявление, като това:
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;
Това води до грешка при конфликт на външен ключ. По-конкретно получавам следната грешка:
(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)
Това се случи, защото, въпреки че поръчката вече беше вмъкната, тази операция беше отменена, когато се върнахме към точката за запис. След това сделката продължи към завършване. Но когато срещна крайния елемент на поръчката, нямаше съответна поръчка (защото това беше отменено) и получихме грешката.
Когато проверявам базата данни, клиентът беше вмъкнат, но отново не беше въведена информация за поръчката.
Можете да препратите към една и съща точка на запис от множество места в транзакцията, ако е необходимо.
На практика бихте използвали условно програмиране, за да върнете транзакцията в savepont.
Вложени транзакции
Можете също да вложите транзакции в други транзакции, ако е необходимо.
Като това:
BEGIN TRANSACTION Transaction1;
UPDATE table1 ...;
BEGIN TRANSACTION Transaction2;
UPDATE table2 ...;
SELECT * from table1;
COMMIT TRANSACTION Transaction2;
UPDATE table3 ...;
COMMIT TRANSACTION Transaction1;
Както споменахме, точният синтаксис, който използвате за създаване на транзакция, ще зависи от вашата СУБД, така че проверете документацията на вашата СУБД за пълна картина на вашите опции при създаване на транзакции в SQL.