Враховуючи лише код, показаний у запитанні, і припускаючи, що жоден з трьох підпроектів не має явної обробки транзакцій, то так, помилка в будь-якому з трьох підпроектів буде виявлена, і ROLLBACK
в CATCH
блоці буде відкатуватися все твору.
Але тут слід зазначити кілька речей про транзакції (принаймні, у SQL Server):
Існує лише одна реальна транзакція (перша), незалежно від того, скільки разів ви телефонуєтеBEGIN TRAN
- Ви можете назвати транзакцію (як ви зробили тут) , і це ім'я буде з'являтися в журналах, але іменування мають сенс тільки для першої / зовнішньої більшої угоди (бо знову ж , перше з них є угода).
- Кожен раз, коли ви телефонували
BEGIN TRAN
, незалежно від того, чи він названий, чисельність транзакцій збільшується на 1.
- Ви можете бачити поточний рівень, зробивши це
SELECT @@TRANCOUNT;
- Будь-які
COMMIT
команди, видані @@TRANCOUNT
в 2 або вище, не мають нічого іншого, як зменшити, по черзі, лічильник транзакцій.
- Ніколи не вчиняється, поки атрибут
COMMIT
не видається, коли його @@TRANCOUNT
немає1
- Про всяк випадок, якщо вищенаведена інформація не вказується чітко: незалежно від рівня транзакції, фактичного вкладення транзакцій немає.
Точки збереження дозволяють створити підмножину роботи в рамках транзакції, яку можна скасувати.
- Точки збереження створюються / позначаються за допомогою
SAVE TRAN {save_point_name}
команди
- Збереження очок позначає початок підмножини роботи, яку можна скасувати, не відкочуючи всю транзакцію.
- Імена точок збереження не повинні бути унікальними, але використання одного і того ж імені не один раз все одно створює чіткі точки збереження.
- Точки збереження можна вкласти.
- Бали збереження не можуть бути здійснені.
- Збереження очок можна скасувати через
ROLLBACK {save_point_name}
. (докладніше про це нижче)
- Відкат точки збереження скасує будь-яку роботу, що відбулася після останнього дзвінка
SAVE TRAN {save_point_name}
, включаючи будь-які точки збереження, створені після того, як створений той відкат (звідси "вкладення").
- Відкат точки збереження не впливає на кількість / рівень транзакцій
- Будь-яка робота, виконана до початкової,
SAVE TRAN
не може бути скасована, окрім випуску ROLLBACK
всієї транзакції.
- Просто щоб бути ясно: оформивши ,
COMMIT
коли @@TRANCOUNT
знаходиться на 2 або вище, не робить ніякого впливу на точки збереження (бо знову ж , угода рівнів вище 1 не існує поза цим лічильника).
Ви не можете здійснювати конкретні названі транзакції. Трансакція "ім'я", якщо вона надається разом із COMMIT
, ігнорується і існує лише для читабельності.
ROLLBACK
Видається без імені завжди буде відкотити всі транзакції.
ROLLBACK
Видається ім'я повинно відповідати або:
- Перша транзакція, якщо припустити, що вона названа: якщо
припустити, що ніхто SAVE TRAN
не викликався з однаковою назвою транзакції, це відкаже ВСІ транзакції.
- "Точка збереження" (описана вище).
Ця поведінка "скасує" всі зміни, зроблені з часу виклику останньої SAVE TRAN {save_point_name}
.
- Якщо в першій транзакції було а) ім'я та б) були
SAVE TRAN
команди, видані з її ім'ям, то кожен ROLLBACK цього імені транзакції скасовує кожну точку збереження, поки не залишиться жодної точки цього імені. Після цього, ROLLBACK, виданий з такою назвою, відкаже ВСІ транзакції.
Наприклад, припустимо, що такі команди виконувались у наведеному порядку:
BEGIN TRAN A -- @@TRANCOUNT is now 1
-- DML Query 1
SAVE TRAN A
-- DML Query 2
SAVE TRAN A
-- DML Query 3
BEGIN TRAN B -- @@TRANCOUNT is now 2
SAVE TRAN B
-- DML Query 4
Тепер, якщо ви видаєте (кожен із наведених нижче сценаріїв не залежить один від одного):
ROLLBACK TRAN B
один раз: це скасує "DML-запит 4". @@TRANCOUNT
ще 2.
ROLLBACK TRAN B
двічі: він скасує "DML Query 4", а потім помилку, оскільки для "B" немає відповідної точки збереження. @@TRANCOUNT
ще 2.
ROLLBACK TRAN A
один раз: це скасує "DML Query 4" та "DML Query 3". @@TRANCOUNT
ще 2.
ROLLBACK TRAN A
двічі: це скасує "DML Query 4", "DML Query 3" та "DML Query 2". @@TRANCOUNT
ще 2.
ROLLBACK TRAN A
тричі: це скасує "DML Query 4", "DML Query 3" та "DML Query 2". Тоді вона відкатує всю транзакцію (все, що залишилося, - "Запит DML 1"). @@TRANCOUNT
зараз 0.
COMMIT
один раз: @@TRANCOUNT
опускається до 1.
COMMIT
раз, а потім ROLLBACK TRAN B
один раз: @@TRANCOUNT
опускається до 1. Потім він скасує "DML-запит 4" (довівши, що COMMIT нічого не зробив). @@TRANCOUNT
досі 1.
Імена транзакцій та імена точок збереження:
Збережена процедура сама по собі не є неявною транзакцією. Кожен запит, якщо явної транзакції не було розпочато, є неявною транзакцією. Ось чому явні транзакції навколо окремих запитів не потрібні, якщо не може бути програмної причини зробити це ROLLBACK
, інакше будь-яка помилка в запиті - це автоматичний відкат цього запиту.
Під час виклику збереженої процедури він повинен вийти зі значенням @@TRANCOUNT
того ж, що і коли було викликано. Значить, ви не можете:
- Запустіть програму
BEGIN TRAN
в процедурі, не виконуючи її, розраховуючи виконати в процесі виклику / батьків.
- Ви не можете оформити,
ROLLBACK
якщо явна транзакція була запущена до виклику proc, оскільки вона повернеться @@TRANCOUNT
до 0.
Якщо ви вийдете із збереженої процедури з кількістю транзакцій, яка є або більшою, або нижчою, ніж коли вона дивилася, ви отримаєте помилку, схожу на:
Повідомлення 266, Рівень 16, Стан 2, Процедура YourProcName, рядок 0
Кількість трансакцій після ВИКОНАННЯ вказує на невідповідність операторів BEGIN і COMMIT. Попередній підрахунок = X, поточний підрахунок = Y.
Табличні змінні, як і звичайні змінні, не пов'язані транзакціями.
Що стосується обробки транзакцій у документах, які можуть бути викликані незалежно (і, отже, потрібна обробка транзакцій) або виклик від інших програм (отже, не потрібно обробляти транзакції): це може бути здійснено двома різними способами.
Те, як я його обробка в протягом декількох років тепер, здається, працює добре, щоб тільки BEGIN
/ COMMIT
/ ROLLBACK
на зовнішній основний шар. Виклики субпрокату просто пропускають команди транзакцій. Нижче я окреслив те, що я вкладаю в кожну процедуру (ну, кожен, хто потребує обробки транзакцій).
- У верхній частині кожної програми,
DECLARE @InNestedTransaction BIT;
Замість простого BEGIN TRAN
зробіть:
IF (@@TRANCOUNT = 0)
BEGIN
SET @InNestedTransaction = 0;
BEGIN TRAN; -- only start a transaction if not already in one
END;
ELSE
BEGIN
SET @InNestedTransaction = 1;
END;
Замість простого COMMIT
зробіть:
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
COMMIT;
END;
Замість простого ROLLBACK
зробіть:
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
Цей метод повинен працювати однаково, незалежно від того, чи було здійснено транзакцію в SQL Server, чи було запущено на рівні програми.
Для отримання повного шаблону цієї транзакції в рамках TRY...CATCH
конструкції, будь ласка, дивіться мою відповідь на наступне запитання DBA.SE: Чи потрібно нам обробляти транзакції в коді C #, а також у збереженій процедурі .
Якщо вийти за рамки "основ", слід знати деякі додаткові нюанси транзакцій:
За замовчуванням транзакції здебільшого не відмовляються / скасовуються автоматично, коли виникає помилка. Це, як правило, не є проблемою, якщо ви маєте належне поводження з помилками та телефонуєте ROLLBACK
самі. Однак іноді речі ускладнюються, як, наприклад, у випадку помилок, що переривають партію, або при використанні OPENQUERY
(або пов'язаних серверів загалом), і у віддаленій системі виникає помилка. Хоча більшість помилок можна зафіксувати за допомогою TRY...CATCH
, але є дві, які не можуть бути захоплені таким чином (не можу пригадати, які з них є на даний момент, хоча - дослідження). У цих випадках потрібно SET XACT_ABORT ON
правильно відкатати транзакцію.
SET XACT_ABORT ON змушує SQL Server , щоб негайно відкат будь-якої угоди (якщо він активний) і передчасне припинення партії , якщо будь-яка помилка відбувається. Цей параметр існував до SQL Server 2005, який представив TRY...CATCH
конструкцію. Здебільшого TRY...CATCH
вирішує більшість ситуацій, і таким чином в основному застаріває потреба XACT_ABORT ON
. Однак, коли ви користуєтесь OPENQUERY
(і, можливо, ще одним сценарієм, який я зараз не пам'ятаю), вам все одно потрібно буде скористатися SET XACT_ABORT ON;
.
Всередині тригера XACT_ABORT
неявно встановлено ON
. Це викликає будь-яку помилку всередині тригера для скасування всього оператора DML, який запустив тригер.
Ви завжди повинні мати належне поводження з помилками, особливо під час використання транзакцій. TRY...CATCH
Конструкт, введений в SQL Server 2005 надає кошти обробки майже у всіх ситуаціях поліпшення привітального з тестування для @@ERROR
після кожної заяви, яке не допомогло багато з партіями переривання помилок.
TRY...CATCH
проте ввів нову "державу". Якщо ви не використовуєте TRY...CATCH
конструкцію, якщо у вас є активна транзакція і виникає помилка, то можна скористатися декількома шляхами:
XACT_ABORT OFF
та помилка переривання висловлювань: транзакція все ще активна і обробка продовжується наступним оператором , якщо такий є.
XACT_ABORT OFF
і більшість помилок, що переривають партію: транзакція все ще активна і обробка продовжується наступною партією , якщо така є.
XACT_ABORT OFF
і певні помилки, що відміняють партію: транзакція відкочується і обробка продовжується наступною партією , якщо така є.
XACT_ABORT ON
і будь-яка помилка: транзакція відкочується і обробка продовжується наступною партією , якщо така є.
ЗАРАЗ, при використанні TRY...CATCH
помилок, що відміняють партію, не переривають партію, а замість цього передають управління на CATCH
блок. Коли XACT_ABORT
це OFF
, транзакція буде по- , як і раніше активні переважна більшість часу, і ви повинні COMMIT
, або , швидше за все, ROLLBACK
. Але якщо зіткнуться з певними помилками, що переривають партію (як, наприклад, з OPENQUERY
), або коли XACT_ABORT
є ON
, транзакція буде в новому стані, "незручним". У такому стані ви не можете і не COMMIT
можете робити жодних операцій з DML. Все, що ви можете зробити - це ROLLBACK
і SELECT
заяви. Однак у цьому "некомфортному" стані транзакція була відкатана після виникнення помилки, а видача цього ROLLBACK
- це лише формальність, але така, яку необхідно зробити.
Функція XACT_STATE може бути використана для визначення того, чи є транзакція активна, непридатна чи не існує. Рекомендується (деякими, принаймні) перевірити цю функцію в CATCH
блоці, щоб визначити, чи є результат -1
(тобто незручний) замість тестування, якщо @@TRANCOUNT > 0
. Але, маючи XACT_ABORT ON
, це має бути єдиним можливим станом, тому здається, що тестування для @@TRANCOUNT > 0
і XACT_STATE() <> 0
є рівнозначними. З іншого боку, коли XACT_ABORT
є OFF
і є активна транзакція, тоді можливо стан 1
або -1
в CATCH
блоці, або в блоці, що передбачає можливість видачі COMMIT
замість ROLLBACK
(хоча, я не можу думати про випадок, коли хтось хотів биCOMMIT
якщо транзакція є товарною). Більш детальну інформацію та дослідження щодо використання XACT_STATE()
в CATCH
блоці з XACT_ABORT ON
можна знайти у моїй відповіді на наступне запитання DBA.SE: У яких випадках транзакція може бути здійснена зсередини блоку CATCH, коли для XACT_ABORT встановлено значення ВКЛ? . Зауважте, що існує незначна помилка, XACT_STATE()
яка призводить до помилкового повернення 1
в певних сценаріях: XACT_STATE () повертає 1 при використанні в SELECT з деякими системними змінними, але без пункту FROM
Примітки щодо оригінального коду:
- Ви можете видалити ім’я, вказане для транзакції, оскільки воно нікому не допомагає.
- Вам не потрібно кожного дзвінка
BEGIN
та END
навколо ньогоEXEC
spNewBilling3
видає помилку, але ви не хочете , щоб виконати відкатspNewBilling2
абоspNewBilling1
, а потім просто видалити[begin|rollback|commit] transaction createSavebillinginvoice
зspSavesomename
.