В тази статия ще разгледаме вложените транзакции на SQL Server, транзакционен блок с една или няколко транзакции.
Изображението описва прост модел на вложената транзакция.

Вътрешната транзакция е съхранена процедура, която се състои от транзакционни блокове. MSDN препоръчва „да поддържате транзакциите възможно най-кратки“, което е напълно противоположно на първия подход. Според мен не препоръчвам използването на вложени транзакции. Все пак понякога се налага да ги използваме, за да решим някои бизнес проблеми.
Така ще разберем:
- Какво ще се случи, когато външна транзакция бъде отменена или ангажиментирана?
- Какво ще се случи, когато вътрешна транзакция бъде отменена или ангажиментирана?
- Как да се справяме с грешките при вложени транзакции?
Като начало ще създадем демонстрационна таблица и ще тестваме възможни случаи.
USE AdventureWorks -----Create Demo Table---- CREATE TABLE CodingSightDemo (NumberValue VARCHAR(20))
Случай 1:И външните, и вътрешните транзакции са ангажирани.
TRUNCATE TABLE CodingSightDemo
--<*************OUTHER TRANSACTION START*************>
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('One')
--<INNER TRANSACTION START>
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('Two')
COMMIT TRAN
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo VALUES('Three')
COMMIT TRAN
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo

В този случай всички записи се вмъкват успешно в таблицата. Предполагахме, че всеки оператор INSERT не връща грешка.
Случай 2:Външната транзакция е отменена , вътрешната транзакция е ангажирана .
TRUNCATE TABLE CodingSightDemo
--<*************OUTHER TRANSACTION START*************>
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('One')
--<INNER TRANSACTION START>
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('Two')
COMMIT TRAN
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo VALUES('Three')
rollback TRAN
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo

Както можете да видите, записите не се вмъкват в таблицата, защото вътрешната транзакция е част от външната транзакция. Поради тази причина вътрешната транзакция се връща назад.
Случай 3:Външната транзакция е ангажирана , вътрешната транзакция е връщана назад .
TRUNCATE TABLE CodingSightDemo
--<*************OUTHER TRANSACTION START*************>
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('One')
--<INNER TRANSACTION START>
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('Two')
ROLLBACK TRAN
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo VALUES('Three')
COMMIT TRAN
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo


В този случай получихме грешка и вмъкнахме последното изявление в таблицата. В резултат на това възникват някои въпроси:
- Защо получихме грешка?
- Защо последният оператор INSERT беше добавен към таблицата?
По правило операторът ROLLBACK TRAN връща обратно всички отворени транзакции, изпълнени в текущата сесия. Не можем да напишем заявка, защото ще върне грешка.
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('One')
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('Two')
ROLLBACK TRAN
ROLLBACK TRAN

Ще разгледаме как това правило може да повлияе на нашия случай. Инструкцията ROLLBACK TRAN връща вътрешните и външните транзакции. Поради тази причина получаваме грешка при изпълнение на оператора COMMIT TRAN, тъй като няма отворени транзакции.

След това ще добавим изявление за обработка на грешки към тази заявка и ще го модифицираме въз основа на подхода за защитно програмиране (както Уикипедия заявява:Защитното програмиране е форма на защитен дизайн, предназначен да гарантира непрекъснатата функция на част от софтуера при непредвидени обстоятелства). Когато напишем заявка, без да се грижим за обработката на грешки и получим грешка, може да се сблъскаме с нарушаване на целостта на данните.
Със следващия скрипт ще използваме точки за запис. Те маркират точка в транзакцията и ако желаете, можете да върнете обратно всички DML (език за манипулиране на данни) изрази до маркираната точка.
BEGIN TRY
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('One')
--<INNER TRANSACTION START>
SAVE TRANSACTION innerTRAN
BEGIN TRY
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('Two')
COMMIT TRAN
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
BEGIN
ROLLBACK TRANSACTION innerTRAN
PRINT 'Roll back occurs for inner tran'
END
IF XACT_STATE() <> 0
BEGIN
COMMIT TRAN
PRINT 'Commit occurs for firt open tran'
END
END CATCH
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo VALUES('Three')
COMMIT TRAN
END TRY
BEGIN CATCH
BEGIN
IF XACT_STATE() <> 0
ROLLBACK TRAN
PRINT 'Roll back occurs for outer tran'
END
END CATCH
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo Тази заявка ще се справи с грешката, когато вътрешната транзакция получи грешка. Освен това външните транзакции се извършват успешно. Въпреки това, в някои случаи, ако вътрешната транзакция получи грешка, външната транзакция трябва да се върне назад. В този случай ще използваме локална променлива, която ще запази и предаде стойността на състоянието на грешка на вътрешната заявка. Ще проектираме външната заявка с тази стойност на променливата и заявката ще бъде както следва.
--<*************OUTHER TRANSACTION START*************>
DECLARE @innertranerror as int=0
BEGIN TRY
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('One')
--<INNER TRANSACTION START>
SAVE TRANSACTION innerTRAN
BEGIN TRY
BEGIN TRAN
INSERT INTO CodingSightDemo
VALUES('Two')
COMMIT TRAN
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
BEGIN
SET @innertranerror=1
ROLLBACK TRANSACTION innerTRAN
PRINT 'Roll back occurs for inner tran'
END
IF XACT_STATE() <> 0
BEGIN
COMMIT TRAN
PRINT 'Commit occurs for firt open tran'
END
END CATCH
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo VALUES('Three')
if @innertranerror=0
BEGIN
COMMIT TRAN
END
IF @innertranerror=1
BEGIN
ROLLBACK TRAN
END
END TRY
BEGIN CATCH
BEGIN
IF XACT_STATE() <> 0
ROLLBACK TRAN
PRINT 'Roll back occurs for outer tran'
END
END CATCH
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo Заключения
В тази статия проучихме вложени транзакции и анализирахме как да обработваме грешки в този тип заявка. Най-важното правило за този тип транзакция е да пишем защитни заявки, защото можем да получим грешка при външни или вътрешни транзакции. Поради тази причина трябва да проектираме поведението за обработка на грешки на заявката.
Препратки
Вложени транзакции
ЗАПАЗЕТЕ ТРАНЗАКЦИЯТА