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.