В тази статия ще разгледаме вложените транзакции на 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
Заключения
В тази статия проучихме вложени транзакции и анализирахме как да обработваме грешки в този тип заявка. Най-важното правило за този тип транзакция е да пишем защитни заявки, защото можем да получим грешка при външни или вътрешни транзакции. Поради тази причина трябва да проектираме поведението за обработка на грешки на заявката.
Препратки
Вложени транзакции
ЗАПАЗЕТЕ ТРАНЗАКЦИЯТА