У яких випадках транзакція може бути здійснена з блоку CATCH, коли для XACT_ABORT встановлено значення ВКЛ?


13

Я читав MSDN про TRY...CATCHта XACT_STATE.

У ньому є наступний приклад, який використовує XACT_STATEв CATCHблоці TRY…CATCHконструкції для визначення, чи потрібно здійснювати транзакцію чи повертати назад:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Test XACT_STATE for 0, 1, or -1.
    -- If 1, the transaction is committable.
    -- If -1, the transaction is uncommittable and should 
    --     be rolled back.
    -- XACT_STATE = 0 means there is no transaction and
    --     a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT 'The transaction is in an uncommittable state.' +
              ' Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT 'The transaction is committable.' + 
              ' Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

Я не розумію, чому я повинен піклуватися і перевіряти, що XACT_STATEповертається?

Зверніть увагу, що прапор XACT_ABORTвстановлений ONу прикладі.

Якщо всередині TRYблоку є достатньо сильна помилка , управління перейде в CATCH. Отже, якщо я перебуваю всередині CATCH, я знаю, що в транзакції виникли проблеми, і справді єдине розумне, що потрібно зробити в цьому випадку - це повернути її назад, чи не так?

Але цей приклад з MSDN передбачає, що можуть бути випадки, коли контроль передається, CATCHі все ж має сенс здійснити транзакцію. Чи може хтось надати якийсь практичний приклад, коли це може статися, коли це має сенс?

Я не бачу, в яких випадках контроль може передаватися всередину CATCHтранзакції, яку можна здійснити, коли XACT_ABORTвстановленоON .

У статті MSDN SET XACT_ABORTє приклад, коли деякі операції в межах транзакції виконуються успішно, а деякі не вдається, коли XACT_ABORTвстановлено значення OFF, я розумію це. Але, SET XACT_ABORT ONяк може статися, що XACT_STATE()повертає 1 всередині CATCHблоку?

Спочатку я написав би такий код так:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    PRINT 'Rolling back transaction.';
    ROLLBACK TRANSACTION;
END CATCH;
GO

Враховуючи відповідь Макса Вернона, я б написав такий код. Він показав, що має сенс перевірити, чи є активна транзакція перед спробою ROLLBACK. Тим НЕ менше, з SET XACT_ABORT ONв CATCHблоці може бути або приречена угода чи ні угоди взагалі. Тож у будь-якому випадку ні до чого COMMIT. Я помиляюся?

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    IF (XACT_STATE()) <> 0
    BEGIN
        -- There is still an active transaction that should be rolled back
        PRINT 'Rolling back transaction.';
        ROLLBACK TRANSACTION;
    END;

END CATCH;
GO

Відповіді:


8

Виявляється, транзакція не може бути здійснена зсередини CATCHблоку, якщо XACT_ABORTвстановлено значення ON.

Приклад з MSDN дещо вводить в оману, тому що перевірка передбачає, що XACT_STATEв деяких випадках може повернутись 1, а COMMITтранзакції це можливо .

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;

Це неправда, XACT_STATEніколи не поверне 1 внутрішній CATCHблок, якщо XACT_ABORTвстановлено значення ON.

Здається, що зразок коду MSDN мав на меті в основному ілюструвати використання XACT_STATE()функції незалежно від XACT_ABORTналаштування. Зразок коду виглядає достатньо загальним для роботи як із XACT_ABORTвстановленими, так ONі з OFF. Просто з XACT_ABORT = ONчеком IF (XACT_STATE()) = 1стає непотрібним.


Є дуже хороший докладний набір статей про обробку помилок та транзакцій на SQL Server Ерланда Соммарського. У Частині 2 - Класифікація помилок він представляє вичерпну таблицю, в якій зібрані всі класи помилок та те, як вони обробляються SQL Server, а також як TRY ... CATCHі XACT_ABORTзмінюється поведінка.

+-----------------------------+---------------------------++------------------------------+
|                             |     Without TRY-CATCH     ||        With TRY-CATCH        |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
|              SET XACT_ABORT |  OFF  |  ON   | OFF | ON  ||    ON or OFF     | OFF | ON  |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Class Name                  |    Aborts     |   Rolls   ||    Catchable     |   Dooms   |
|                             |               |   Back    ||                  |transaction|
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Fatal errors                |  Connection   |    Yes    ||       No         |    n/a    |
| Batch-aborting              |     Batch     |    Yes    ||       Yes        |    Yes    |
| Batch-only aborting         |     Batch     | No  | Yes ||       Yes        | No  | Yes |
| Statement-terminating       | Stmnt | Batch | No  | Yes ||       Yes        | No  | Yes |
| Terminates nothing at all   |    Nothing    |    No     ||       Yes        | No  | Yes |
| Compilation: syntax errors  |  (Statement)  |    No     ||       Yes        | No  | Yes |
| Compilation: binding errors | Scope | Batch | No  | Yes || Outer scope only | No  | Yes |
| Compilation: optimisation   |     Batch     |    Yes    || Outer scope only |    Yes    |
| Attention signal            |     Batch     | No  | Yes ||       No         |    n/a    |
| Informational/warning msgs  |    Nothing    |    No     ||       No         |    n/a    |
| Uncatchable errors          |    Varying    |  Varying  ||       No         |    n/a    |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+

Останній стовпчик таблиці відповідає на питання. З TRY-CATCHта з XACT_ABORT ONугодами приречена у всіх можливих випадках.

Одна примітка за межами питання. Як говорить Erland, ця послідовність є однією з причин , щоб встановити XACT_ABORTна ON:

Я вже дав рекомендацію, що ваші збережені процедури повинні містити команду SET XACT_ABORT, NOCOUNT ON. Якщо ви подивитесь на таблицю вище, ви побачите, що XACT_ABORTфактично є якийсь більш високий рівень узгодженості. Наприклад, транзакція завжди приречена. Надалі, я покажу багато прикладів , коли я ставлю XACT_ABORTна OFF, так що ви можете отримати уявлення про те, чому ви повинні уникати цього параметра по умовчанням.


7

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

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end
go

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


Я ціную вашу відповідь, але питання в тому , на самому ділі не слід встановити XACT_ABORTна ONабо OFF.
Володимир Баранов

7

TL; DR / Резюме: Щодо цієї частини питання:

Я не бачу, в яких випадках контроль може передаватися всередину CATCHтранзакції, яку можна здійснити, коли XACT_ABORTвстановленоON .

Я зробив зовсім небагато тестування на це зараз , і я не можу знайти якісь - або випадки , коли XACT_STATE()повертається 1всередині CATCHблоку , коли @@TRANCOUNT > 0 і сесії майна XACT_ABORTє ON. Насправді, відповідно до поточної сторінки MSDN для SET XACT_ABORT :

Якщо SET XACT_ABORT увімкнено, якщо оператор Transact-SQL викликає помилку виконання, вся транзакція припиняється і повертається назад.

Це твердження, схоже, узгоджується з вашими міркуваннями та моїми висновками.

У статті MSDN про SET XACT_ABORTприклад є приклад, коли деякі операції всередині транзакції виконуються успішно, а деякі не вдаються, коли XACT_ABORTвстановлено значенняOFF

Щоправда, але твердження в цьому прикладі не знаходяться в TRYблоці. Ці ж висловлювання в TRYблоці все-таки перешкоджають виконанню будь-яких операторів після того, що викликало помилку, але припускаючи, що XACT_ABORTце означає OFF, що коли управління передається CATCHблоку, транзакція все ще є фізично достовірною, оскільки всі попередні зміни відбувалися без помилки і можуть бути здійснені, якщо це бажання, або їх можна відкотити. З іншого боку, якщо XACT_ABORTє , ONто будь-які попередні зміни автоматично відкочується, а потім вам надається вибір, або: а) видатиROLLBACKяка в основному просто прийняття ситуації , так як угода була вже відкат мінус скидання @@TRANCOUNTдо 0, або б) отримати помилку. Не великий вибір, чи не так?

Один, можливо, важливий деталь цієї головоломки, який не видно в цій документації, - SET XACT_ABORTце те, що ця властивість сеансу і навіть той приклад кодів існують ще з часів SQL Server 2000 (документація майже однакова між версіями), передбачуючи TRY...CATCHконструкцію, яка була введений в SQL Server 2005. дивлячись на цій документації знову і подивитися на прикладі ( безTRY...CATCH ), використовуючи XACT_ABORT ONвикликає негайний відкат угоди: немає стану транзакції з «нефіксіруемие» (зверніть увагу , що немає жодної згадки в всі "непередавані" стану транзакцій у цій SET XACT_ABORTдокументації).

Я думаю, що розумно зробити висновок, що:

  1. Введення TRY...CATCHконструкції в SQL Server 2005 створило потребу в новому стані транзакції (тобто "нерозбірливому") і XACT_STATE()функції для отримання цієї інформації.
  2. перевірка XACT_STATE()в CATCHблоці дійсно має сенс лише в тому випадку, якщо істинне наступне:
    1. XACT_ABORTє OFF(інакше XACT_STATE()завжди повинен повертатися -1і @@TRANCOUNTбув би всім, що тобі потрібно)
    2. У вас є логіка в CATCHблоці або десь в ланцюзі, якщо виклики вкладені, що вносить зміни (а COMMITчи навіть будь-який оператор DML, DDL тощо) замість того, щоб робити ROLLBACK. (це дуже нетиповий випадок використання) ** Будь ласка, дивіться примітку внизу, в розділі ОНОВЛЕННЯ 3, щодо неофіційної рекомендації Microsoft завжди перевіряти XACT_STATE()замість цього @@TRANCOUNT, і чому тестування показує, що їх міркування не зникають.
  3. введення TRY...CATCHконструкції в SQL Server 2005 здебільшого застаріло XACT_ABORT ONвластивість сеансу, оскільки вона забезпечує більший ступінь контролю над транзакцією (у вас, принаймні, є можливість COMMIT, за умови, що XACT_STATE()вона не повертається -1).
    Інший спосіб дивитися на це, до SQL Server 2005 , при XACT_ABORT ONумови , простий і надійний спосіб обробки стоп , коли сталася помилка, в порівнянні з перевіркою @@ERRORпісля кожного оператора.
  4. Код прикладу документації для XACT_STATE()помилковий або в кращому випадку вводить в оману в тому, що він показує перевірку, XACT_STATE() = 1коли XACT_ABORTце ON.

Довга частина ;-)

Так, цей приклад код на MSDN трохи заплутаний (див. Також: @@ TRANCOUNT (відкат) проти XACT_STATE ) ;-). І я вважаю, що це вводить в оману, оскільки він або показує щось, що не має сенсу (з тієї причини, про яку ви питаєте: чи можете ви навіть мати "зобов’язану" транзакцію в CATCHблоці, коли XACT_ABORTє ON), або навіть якщо це можливо, це все ще фокусується на технічній можливості, яку мало хто коли-небудь захоче чи потребуватиме, і ігнорує причину, коли хтось її більше потребує.

Якщо всередині блоку TRY виникла досить сильна помилка, керування перейде в CATCH. Отже, якщо я перебуваю всередині CATCH, я знаю, що в транзакції виникли проблеми, і справді єдине розумне, що потрібно зробити в цьому випадку - це повернути її назад, чи не так?

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

  • "достатньо сильна помилка": Просто щоб було зрозуміло, Спробуйте ... CATCH буде захоплювати більшість помилок. Список того, що не буде спіймано, міститься на тій пов’язаній сторінці MSDN у розділі "Помилки, на які не впливає TRY… CATCH Construct".

  • "якщо я всередині CATCH, я знаю, що з транзакцією виникли проблеми" ( додається em phas ): якщо під "транзакцією" ви маєте на увазі логічну одиницю роботи, визначену вами, групуючи заяви в явну транзакцію, то швидше за все так. Я думаю, що більшість із нас, люди з БД, як правило, погоджуються, що відкат - це "єдине розумне, що можна зробити", оскільки ми, мабуть, маємо подібне уявлення про те, як і чому ми використовуємо явні транзакції та мислимо, які кроки повинні складати атомну одиницю роботи.

    Але якщо ви маєте на увазі фактичні одиниці роботи, згруповані в явну транзакцію, то ні, ви не знаєте, що сама транзакція мала проблему. Ви тільки знаєте , що оператор виконує в явному вигляді певної угоди з'являлося повідомлення про помилку. Але це може бути не DML, ні DDL. І навіть якщо це був оператор DML, сама транзакція все ще може бути прихильною.

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

Коли XACT_STATE()повертається a 1, це означає, що транзакція є "фіксацією", що у вас є вибір між COMMITабо ROLLBACK. Ви можете не захотіти зробити це, але якщо з якоїсь важкої для вас навіть ідеї-приклад-з-за прикладу ви хочете, принаймні, ви могли, тому що деякі частини транзакції завершилися успішно.

Але коли XACT_STATE()повертає a -1, то вам дійсно потрібно, ROLLBACKтому що частина транзакції перейшла в поганий стан. Тепер я погоджуюся, що якщо контроль було передано до блоку CATCH, то є достатньо сенсу просто перевірити @@TRANCOUNT, тому що навіть якщо ви можете здійснити транзакцію, чому б ви цього хотіли?

Але якщо ви помітите вгорі прикладу, налаштування XACT_ABORT ONдещо змінює речі. Ви можете мати звичайну помилку, після цього BEGIN TRANпередасте управління блоку CATCH, коли XACT_ABORTє OFFі XACT_STATE () повернеться 1. Але, якщо XACT_ABORT є ON, то транзакція "переривається" (тобто недійсна) за будь-яку "ol помилку" і потім XACT_STATE()повернеться -1. У цьому випадку здається марним перевіряти XACT_STATE()всередині CATCHблоку, оскільки він, як видається, завжди повертає a -1коли XACT_ABORTє ON.

То для чого тоді XACT_STATE()? Деякі підказки:

  • На сторінці MSDN у TRY...CATCHрозділі "Неконтрольні транзакції та XACT_STATE" зазначено:

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

  • На сторінці MSDN для SET XACT_ABORT у розділі "Зауваження" зазначено:

    Якщо SET XACT_ABORT ВИМКНЕНО, в деяких випадках відновлюється лише оператор Transact-SQL, який викликав помилку, і транзакція продовжується обробкою.

    і:

    XACT_ABORT повинен бути включений для операторів зміни даних у неявній або явній транзакції проти більшості постачальників OLE DB, включаючи SQL Server.

  • Сторінка MSDN для ПОЧАТОЧНОЇ ТРАНЗАКЦІЇ в розділі «Зауваження» говорить:

    Локальна транзакція, розпочата оператором BEGIN TRANSACTION, переходить до розподіленої транзакції, якщо виконуються наступні дії перед тим, як оператор буде скоєний або скасований назад:

    • Виконання INSERT, DELETE або UPDATE, що посилається на віддалену таблицю на пов'язаному сервері, виконується. Оператор INSERT, UPDATE або DELETE виходить з ладу, якщо постачальник OLE DB, який використовується для доступу до пов'язаного сервера, не підтримує інтерфейс ITransactionJoin.

Здається, найбільш застосовне використання виявляється в контексті операторів DML пов'язаного сервера. І я вірю, що я сам наткнувся на це роки тому. Я не пам'ятаю всіх подробиць, але це мало відношення до того, що віддалений сервер не був доступний, і чомусь ця помилка не потрапила в блок TRY і ніколи не була відправлена ​​в CATCH, і це так КОМІТЕТ, коли його не повинно було. Звичайно, це могло бути проблемою не XACT_ABORTвстановлення, ONа не відмови від перевірки XACT_STATE(), або, можливо, обох. І я пам'ятаю, як читав щось, що говорило, якщо ви використовуєте пов'язані сервери та / або розподілені транзакції, то вам потрібно було користуватися XACT_ABORT ONта / або XACT_STATE(), але я не можу зараз знайти цей документ. Якщо я все-таки знайду, я оновлю це за посиланням.

Тим не менш, я спробував кілька речей і не можу знайти сценарій, який містить XACT_ABORT ONі передає контроль до CATCHблоку з XACT_STATE()поданням звітів 1.

Спробуйте в цих прикладах, щоб побачити вплив значення XACT_ABORTна значення XACT_STATE():

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;

ОНОВЛЕННЯ

Хоча не є частиною оригінального питання, заснованого на цих коментарях до цього відповіді:

Я читав статті Ерланда про поводження з помилками та трансакціями, де він каже, що XACT_ABORTце OFFза замовчуванням із застарілих причин, і зазвичай ми мусимо це встановити ON.
...
"... якщо дотримуватися рекомендації та працювати з SET XACT_ABORT ON, транзакція завжди буде приречена."

Перш ніж використовувати XACT_ABORT ONвсюди, я б задумався: що саме тут отримують? Я не вважав за потрібне це робити і взагалі виступаю за те, щоб ви використовували його лише в разі необхідності. Або ви не хочете ROLLBACKможе бути ручки досить легко, використовуючи шаблон , показаний на @ Ремус відповіді , або той , який я використовую в протягом багатьох років , що, по суті , те ж саме , але без точки збереження, як показані в цій відповіді (який обробляє вкладені дзвінки):

Чи потрібно нам обробляти транзакції в коді C #, а також у збереженій процедурі


ОНОВЛЕННЯ 2

Я зробив трохи більше тестування, на цей раз створивши невеликий додаток .NET Console, створивши транзакцію в шарі додатка, перед виконанням будь-яких SqlCommandоб'єктів (тобто через using (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...), а також за допомогою помилки, що перериває пакет, замість просто заяви помилка, яка виявила:

  1. "Незручна" транзакція - це здебільшого вже скасовано (зміни відмінено), але @@TRANCOUNTвсе ще> 0.
  2. Якщо у вас є "непридатна" транзакція, ви не можете випустити a COMMIT, що призведе до генерування та помилки, сказавши, що транзакція є "непридатною". Ви також не можете його проігнорувати / нічого не робити, оскільки помилка буде створена, коли партія закінчиться, заявивши, що партія завершена затяжною, нерозбірливою транзакцією, і вона буде відкатана (так, гм, якщо вона все одно автоматично відкатується, чому турбує кидати помилку?). Отже, ви повинні видати явний ROLLBACK, можливо, не в найближчому CATCHблоці, але до того, як партія закінчиться.
  3. У TRY...CATCHконструкції, коли XACT_ABORTє OFF, помилки, які автоматично припинять транзакцію, якщо вони сталися поза TRYблоком, наприклад, помилки, що переривають партію, скасовують роботу, але не припиняють Tranasction, залишаючи його "незручним". Видача а ROLLBACK- це більше формальність, необхідна для закриття транзакції, але робота вже відмовлена.
  4. Коли XACT_ABORTце так ON, більшість помилок діють як перериваючі партії, а значить, поводяться так, як описано в точці кулі безпосередньо вище (№3).
  5. XACT_STATE(), принаймні в CATCHблоці, буде відображено -1помилку, що перериває партію, якщо в момент помилки була активна транзакція.
  6. XACT_STATE()іноді повертається 1навіть тоді, коли немає активної транзакції. Якщо @@SPID(серед інших) є у SELECTсписку разом із XACT_STATE(), тоді XACT_STATE()повернеться 1, коли немає активної транзакції. Така поведінка почалася в SQL Server 2012 і існує з 2014 року, але я не пройшов тестування в 2016 році.

Маючи на увазі вищезазначені моменти:

  • З огляду на пункти №4 та №5, оскільки більшість (або всі?) Помилки зроблять транзакцію "непридатною", перевіряти XACT_STATE()в CATCHблоці здається цілком безглуздим час XACT_ABORT, ONоскільки значення, яке повертається, буде завжди -1.
  • Перевірка XACT_STATE()в CATCHблоці , коли XACT_ABORTце OFFмає сенс , тому що повертається значення буде по крайней мере, деякі зміни , так як він буде повертати 1помилки , заяву-переривання. Однак якщо ви кодуєте, як і більшість із нас, то ця відмінність є безглуздою, оскільки ви все ROLLBACKодно будете телефонувати просто за те, що сталася помилка.
  • Якщо ви виявили ситуацію, яка вимагає видачі символу COMMITв CATCHблоці, то перевірте значення XACT_STATE()та обов'язково SET XACT_ABORT OFF;.
  • XACT_ABORT ONначебто, мало користі над TRY...CATCHконструкцією.
  • Я не можу знайти жодного сценарію, де перевірка не XACT_STATE()дає значущої користі від простої перевірки @@TRANCOUNT.
  • Я також не можу знайти жодного сценарію, де XACT_STATE()повертається 1в CATCHблок, коли XACT_ABORTє ON. Я думаю, що це помилка в документації.
  • Так, ви можете відкатати транзакцію, яку ви явно не почали. І в контексті використання XACT_ABORT ON, це суперечливий момент, оскільки помилка, що трапляється в TRYблоці, автоматично відкатує зміни.
  • TRY...CATCHКонструкція має перевагу по порівнянні XACT_ABORT ONз автоматично не скасує всю угоду, і , отже , дозволяє Операцію (якщо XACT_STATE()повертається 1) буде здійснено (навіть якщо це ребро випадок).

Приклад XACT_STATE()повернення , -1коли XACT_ABORTце OFF:

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT CONVERT(INT, 'g') AS [ConversionError];

    COMMIT TRAN;
END TRY
BEGIN CATCH
    DECLARE @State INT;
    SET @State = XACT_STATE();
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            @State AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage];

    IF (@@TRANCOUNT > 0)
    BEGIN
        SELECT 'Rollin back...' AS [Transaction];
        ROLLBACK;
    END;
END CATCH;

ОНОВЛЕННЯ 3

Пов’язане з пунктом № 6 у розділі ОНОВЛЕННЯ 2 (тобто можливе неправильне значення, повернене XACT_STATE()коли немає активної транзакції):

ВИМОГАЙТЕ ЗАЯВКУ, що в "XACT_STATE () може повернутись неправильний стан транзакції в SQL 2012".

@@ trancount повертає кількість BEGIN операторів TRAN. Таким чином, це не є надійним показником наявності активної транзакції. XACT_STATE () також повертає 1, якщо є активна транзакція автокомісії, і, таким чином, є більш надійним показником наявності активної транзакції.

Однак я не можу знайти причин не довіряти @@TRANCOUNT. Наступний тест показує, що @@TRANCOUNTдійсно повертається 1в операції з автоматичним фіксацією:

--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
       XACT_STATE() AS [XactState];
GO
--- end setup

DECLARE @Test TABLE (TranCount INT, XactState INT);

SELECT * FROM @Test; -- no rows

EXEC #TransactionInfo; -- 0 for both fields

INSERT INTO @Test (TranCount, XactState)
    EXEC #TransactionInfo;

SELECT * FROM @Test; -- 1 row; 1 for both fields

Я також перевіряв справжню таблицю з тригером, і @@TRANCOUNTв межах тригера точно звітував, 1навіть не маючи явної транзакції.


4

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

Перевірка XACT_STATE (), щоб визначити, чи можна виконати відкат, є просто хорошою практикою. Сліпо спроба відкату означає, що ви можете ненавмисно викликати помилку всередині TRY ... CATCH.

Один із способів відкату може виявитися невдалим у TRY ... CATCH був би, якщо ви явно не розпочали транзакцію. Копіювання та вставлення блоків коду може легко викликати це.


Дякую за відповідь Я просто не міг придумати випадок, коли простий ROLLBACKне працював би всередині CATCHі ви дали хороший приклад. Я здогадуюсь, він також може швидко ставати безладним, якщо TRY ... CATCH ... ROLLBACKзадіяні вкладені транзакції та вкладені збережені процедури .
Володимир Баранов

Але я би вдячний, якщо ви зможете поширити свою відповідь щодо другої частини - IF (XACT_STATE()) = 1 COMMIT TRANSACTION; Як ми можемо опинитися всередині CATCHблоку з прихильною транзакцією? Я б не наважувався збирати якийсь (можливий) сміття зсередини CATCH. Мої міркування такі: якщо ми знаходимося всередині CATCHчогось, що пішло не так, я не можу довіряти стану бази даних, тому я краще ROLLBACKвсе, що у нас є.
Володимир Баранов
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.