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
документації).
Я думаю, що розумно зробити висновок, що:
- Введення
TRY...CATCH
конструкції в SQL Server 2005 створило потребу в новому стані транзакції (тобто "нерозбірливому") і XACT_STATE()
функції для отримання цієї інформації.
- перевірка
XACT_STATE()
в CATCH
блоці дійсно має сенс лише в тому випадку, якщо істинне наступне:
XACT_ABORT
є OFF
(інакше XACT_STATE()
завжди повинен повертатися -1
і @@TRANCOUNT
був би всім, що тобі потрібно)
- У вас є логіка в
CATCH
блоці або десь в ланцюзі, якщо виклики вкладені, що вносить зміни (а COMMIT
чи навіть будь-який оператор DML, DDL тощо) замість того, щоб робити ROLLBACK
. (це дуже нетиповий випадок використання) ** Будь ласка, дивіться примітку внизу, в розділі ОНОВЛЕННЯ 3, щодо неофіційної рекомендації Microsoft завжди перевіряти XACT_STATE()
замість цього @@TRANCOUNT
, і чому тестування показує, що їх міркування не зникають.
- введення
TRY...CATCH
конструкції в SQL Server 2005 здебільшого застаріло XACT_ABORT ON
властивість сеансу, оскільки вона забезпечує більший ступінь контролю над транзакцією (у вас, принаймні, є можливість COMMIT
, за умови, що XACT_STATE()
вона не повертається -1
).
Інший спосіб дивитися на це, до SQL Server 2005 , при XACT_ABORT ON
умови , простий і надійний спосіб обробки стоп , коли сталася помилка, в порівнянні з перевіркою @@ERROR
після кожного оператора.
- Код прикладу документації для
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()) { ...
), а також за допомогою помилки, що перериває пакет, замість просто заяви помилка, яка виявила:
- "Незручна" транзакція - це здебільшого вже скасовано (зміни відмінено), але
@@TRANCOUNT
все ще> 0.
- Якщо у вас є "непридатна" транзакція, ви не можете випустити a
COMMIT
, що призведе до генерування та помилки, сказавши, що транзакція є "непридатною". Ви також не можете його проігнорувати / нічого не робити, оскільки помилка буде створена, коли партія закінчиться, заявивши, що партія завершена затяжною, нерозбірливою транзакцією, і вона буде відкатана (так, гм, якщо вона все одно автоматично відкатується, чому турбує кидати помилку?). Отже, ви повинні видати явний ROLLBACK
, можливо, не в найближчому CATCH
блоці, але до того, як партія закінчиться.
- У
TRY...CATCH
конструкції, коли XACT_ABORT
є OFF
, помилки, які автоматично припинять транзакцію, якщо вони сталися поза TRY
блоком, наприклад, помилки, що переривають партію, скасовують роботу, але не припиняють Tranasction, залишаючи його "незручним". Видача а ROLLBACK
- це більше формальність, необхідна для закриття транзакції, але робота вже відмовлена.
- Коли
XACT_ABORT
це так ON
, більшість помилок діють як перериваючі партії, а значить, поводяться так, як описано в точці кулі безпосередньо вище (№3).
XACT_STATE()
, принаймні в CATCH
блоці, буде відображено -1
помилку, що перериває партію, якщо в момент помилки була активна транзакція.
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()
коли немає активної транзакції):
- Дивна / помилкова поведінка розпочалася в SQL Server 2012 (дотепер протестовано проти SP2 2012 та 2014 SP1)
- У версіях SQL Server 2005, 2008 та 2008 R2
XACT_STATE()
не було повідомлено очікуваних значень при використанні в тригерах або INSERT...EXEC
сценаріях: xact_state () не може бути надійно використаний для визначення того, чи є транзакція приреченою . Однак в цих 3 -х варіантах (я тестував тільки на 2008 R2), XACT_STATE()
це НЕ помилково повідомляють , 1
коли використовується в SELECT
с @@SPID
.
Існує помилка Connect, подана проти поведінки, згаданої тут, але закрита як "За задумом ": XACT_STATE () може повернути неправильний стан транзакції в SQL 2012 . Однак тест робився під час вибору з DMV, і було зроблено висновок, що це, природно, матиме транзакцію, генеровану системою, принаймні для деяких DMV. У заключній відповіді МС було також зазначено, що:
Зауважте, що оператор IF, а також SELECT без ВІД, не починають транзакцію.
наприклад, запустивши SELECT XACT_STATE (), якщо у вас немає раніше існуючої транзакції, повернеться 0.
Ці твердження є невірними, якщо наведено наступний приклад:
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
GO
DECLARE @SPID INT;
SET @SPID = @@SPID;
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
GO
Отже, нова помилка Connect:
XACT_STATE () повертає 1 при використанні в SELECT з деякими системними змінними, але без пункту FROM
ВИМОГАЙТЕ ЗАЯВКУ, що в "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
навіть не маючи явної транзакції.
XACT_ABORT
наON
абоOFF
.