Як відкати, коли з однієї збереженої процедури запускаються 3 збережені процедури


23

У мене є збережена процедура, яка виконує лише 3 збережені процедури всередині них. Я використовую лише 1 параметр для зберігання, якщо головний SP успішний.

Якщо перша збережена процедура справно працює в основній збереженій процедурі, але 2-а збережена процедура не працює, то вона автоматично відкатує всі SP в головній SP або мені доведеться виконати якусь команду?

Ось моя процедура:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO

Якщо spNewBilling3видає помилку, але ви не хочете , щоб виконати відкат spNewBilling2або spNewBilling1, а потім просто видалити [begin|rollback|commit] transaction createSavebillinginvoiceз spSavesomename.
Майк

Відповіді:


56

Враховуючи лише код, показаний у запитанні, і припускаючи, що жоден з трьох підпроектів не має явної обробки транзакцій, то так, помилка в будь-якому з трьох підпроектів буде виявлена, і 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.
  • Імена транзакцій та імена точок збереження:

    • може містити до 32 символів
    • трактуються як бінарне зіставлення (не чутливе до регістру, як зазначено в документації), незалежно від зібрань на рівні екземпляра або бази даних.
    • Для детальної інформації дивіться розділ Імена транзакцій у наступному дописі: Що в імені ?: Всередині прискіпливого світу ідентифікаторів T-SQL
  • Збережена процедура сама по собі не є неявною транзакцією. Кожен запит, якщо явної транзакції не було розпочато, є неявною транзакцією. Ось чому явні транзакції навколо окремих запитів не потрібні, якщо не може бути програмної причини зробити це 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

2
Це дійсно гарна, гарна відповідь.
McNets

1
Ого, це одна вичерпна відповідь! Дякую! До речі, наступна сторінка стосується помилок, на які натякаєш, не захоплених Спробуйте ... Ловити? (Під заголовком "Помилки Неуражені з допомогою Try ... Catch Construct"? Technet.microsoft.com/en-us/library/ms175976(v=sql.110).aspx
jrdevdba

1
@jrdevdba Дякую :-). І ласкаво просимо. Щодо помилок, що не в пастці, я майже мав на увазі саме ці дві: Compile errors, such as syntax errors, that prevent a batch from runningі Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.. Але вони трапляються не дуже часто, і коли ви виявите таку ситуацію, або виправте її (якщо це помилка в коді) або помістіть її в підпроцес ( EXECабо sp_executesql), щоб він TRY...CATCHміг вловлювати її.
Соломон Руцький

2

Так, якщо через який-небудь код відкидання помилки у вашій операції вилову збереженої процедури буде виконано, він відкине всі операції, виконані будь-яким прямим оператором або через будь-яку з ваших вкладених процедур, що зберігаються в ньому.

Навіть якщо ви не застосували явної транзакції у своїх вкладених збережених процедурах, все-таки ця збережена процедура використовуватиме неявну транзакцію і буде здійснюватись після завершення, АЛЕ будь-яка ви здійснили через явну або неявну транзакцію в вкладених збережених процедурах, що зберігаються, двигун SQL Server ігнорує її і буде відкат усіх дій цих вкладених збережених процедур, якщо основну збережену процедуру не вдалося і транзакцію відкатують.

Кожен раз, коли транзакція або вчиняється, або повертається назад, виходячи з дій, зроблених в кінці найбільш віддаленої транзакції. Якщо зовнішня транзакція здійснена, також здійснюються внутрішні вкладені транзакції. Якщо зовнішня транзакція повертається назад, то всі внутрішні транзакції також повертаються назад, незалежно від того, чи були внутрішні транзакції окремо вчинені.

Для довідки http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.