Я читал 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
источник
XACT_ABORT
наON
илиOFF
.TL; DR / Резюме: Относительно этой части Вопроса:
Я провел немало тестов по этому вопросу сейчас и не могу найти случаев, когда
XACT_STATE()
возвращается1
внутриCATCH
блока когда@@TRANCOUNT > 0
и свойство сеансаXACT_ABORT
isON
. И действительно, в соответствии с текущей страницей MSDN для SET XACT_ABORT :Это утверждение, похоже, согласуется с вашими предположениями и моими выводами.
Верно, но операторы в этом примере не находятся внутри
TRY
блока. Те же заявления в пределахTRY
блока равно предотвратить выполнение любых заявлений после того, что произошла ошибка, но если предположить , чтоXACT_ABORT
этоOFF
, когда управление передается вCATCH
блок транзакции остается физически действительным в том , что все предыдущие изменения случилось без ошибок и могут быть совершены, если это желание, или они могут быть отменены. С другой стороны, еслиXACT_ABORT
это так,ON
то любые предыдущие изменения автоматически отменяются, и тогда вам предоставляется выбор: а) выполнитьROLLBACK
которая в основном просто принятие ситуации , так как сделка была уже откат минус сброс@@TRANCOUNT
к0
, или B) выдается сообщение об ошибке. Не так много выбора, не так ли?Возможно, важная деталь этой загадки, которая не очевидна в этой документации,
SET XACT_ABORT
заключается в том, что это свойство сеанса и даже этот пример кода существовали начиная с SQL Server 2000 (документация почти идентична между версиями), предшествуяTRY...CATCH
конструкции, которая была введенный в SQL Server 2005. глядя на этой документации снова и посмотреть на примере ( безTRY...CATCH
), используяXACT_ABORT ON
вызывает немедленный откат сделки: нет состояния транзакции из «нефиксируемого» (обратите внимание , что нет никакого упоминания в все «непередаваемое» состояние транзакции в этойSET XACT_ABORT
документации).Я думаю, что разумно сделать вывод, что:
TRY...CATCH
конструкции в SQL Server 2005 создало необходимость в новом состоянии транзакции (т.е. «uncommittable») иXACT_STATE()
функции для получения этой информации.XACT_STATE()
вCATCH
блоке действительно имеет смысл, только если выполняются оба следующих условия:XACT_ABORT
естьOFF
(иначеXACT_STATE()
должен всегда возвращаться-1
и@@TRANCOUNT
будет все, что вам нужно)CATCH
блоке или где-то в цепочке, если вызовы вложены, что делает изменение (aCOMMIT
или даже любой оператор DML, DDL и т. Д.) Вместо выполнения aROLLBACK
. (это очень нетипичный вариант использования) ** см. примечание внизу, в разделе ОБНОВЛЕНИЕ 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 ) ;-). И я чувствую, что это вводит в заблуждение, потому что он либо показывает что-то, что не имеет смысла (по той причине, о которой вы спрашиваете: можете ли вы иметь транзакцию committable в
CATCH
блоке, когдаXACT_ABORT
естьON
), или даже если это возможно, это все еще сосредотачивается на технической возможности, которая когда-либо будет хотеться или нуждаться в немногих, и игнорирует причину, в которой это более вероятно потребуетсяЯ думаю, что было бы полезно, если бы мы убедились, что находимся на одной странице относительно того, что подразумевается под определенными словами и понятиями:
«Достаточно серьезная ошибка»: просто чтобы понять, TRY ... CATCH перехватит большинство ошибок. Список того, что не будет перехвачено, приведен на этой связанной странице MSDN в разделе «Ошибки, не затронутые конструкцией TRY… CATCH».
«если я нахожусь внутри CATCH, я знаю, что у транзакции возникла проблема» ( добавляется em phas ): если под «транзакцией» вы подразумеваете логическую единицу работы, определенную вами, сгруппировав операторы в явную транзакцию, то скорее всего да. Я думаю, что большинство из нас, работающих с БД, склонны согласиться с тем, что откат - это «единственно разумная вещь», поскольку мы, вероятно, придерживаемся аналогичного представления о том, как и почему мы используем явные транзакции, и понимаем, какие шаги должны составлять атомарную единицу. работы.
Но если вы имеете в виду фактические единицы работы, которые группируются в явную транзакцию, то нет, вы не знаете, что в самой транзакции возникла проблема. Вы только знаете , что оператор выполняет в явном виде определенной сделки появлялось сообщение об ошибке. Но это может быть не оператор DML или DDL. И даже если это был оператор DML, сама Транзакция все еще может быть коммитируемой.
Учитывая два вышеизложенных момента, мы, вероятно, должны провести различие между транзакциями, которые вы «не можете» совершить, и транзакциями, которые вы «не хотите» совершать.
Когда
XACT_STATE()
возвращается a1
, это означает, что транзакция является "коммитируемой", что у вас есть выбор междуCOMMIT
илиROLLBACK
. Возможно, вы не захотите его зафиксировать, но если по какой-то причине, которую трудно было даже придумать, по причине, которую вы хотели, по крайней мере, вы могли бы это сделать, потому что некоторые части транзакции успешно завершились.Но когда
XACT_STATE()
возвращается a-1
, тогда вам действительно нужно,ROLLBACK
потому что какая-то часть транзакции перешла в плохое состояние. Теперь я согласен с тем, что если управление было передано блоку CATCH, то имеет смысл просто проверить@@TRANCOUNT
, потому что, даже если вы можете зафиксировать транзакцию, зачем вам это нужно?Но если вы заметите в верхней части примера, настройка
XACT_ABORT ON
немного изменится. У вас может быть обычная ошибка, послеBEGIN TRAN
этого она передаст управление блоку CATCH, когдаXACT_ABORT
будетOFF
и XACT_STATE () вернется1
. НО, если XACT_ABORT равенON
, то транзакция «прерывается» (то есть становится недействительной) для любой ошибки и затемXACT_STATE()
возвращается-1
. В этом случае, кажется, бесполезно проверятьXACT_STATE()
внутриCATCH
блока, так как всегда кажется, что возвращает-1
когдаXACT_ABORT
естьON
.Так тогда для чего
XACT_STATE()
? Некоторые подсказки:Страница MSDN для
TRY...CATCH
раздела «Uncommittable Transactions and XACT_STATE» гласит:Страница MSDN для SET XACT_ABORT в разделе «Замечания» гласит:
и:
Страница MSDN для BAGIN TRANSACTION в разделе «Замечания» гласит:
Наиболее применимое использование, кажется, находится в контексте операторов DML Linked Server. И я верю, что столкнулся с этим сам много лет назад. Я не помню всех деталей, но это было связано с тем, что удаленный сервер был недоступен, и по какой-то причине эта ошибка не попала в блок TRY и никогда не отправлялась в CATCH, и поэтому КОМИТ, когда не должно быть. Конечно, это могло быть связано с тем, что вы не
XACT_ABORT
установили,ON
а не проверилиXACT_STATE()
, или, возможно, и то, и другое. И я вспоминаю, что читал что-то, в котором говорилось, что если вы используете связанные серверы и / или распределенные транзакции, тогда вам нужно было использоватьXACT_ABORT ON
и / илиXACT_STATE()
, но я не могу сейчас найти этот документ. Если я найду его, я обновлю его ссылкой.Тем не менее, я попробовал несколько вещей и не могу найти сценарий, который имеет
XACT_ABORT ON
и передает управлениеCATCH
блоку сXACT_STATE()
отчетностью1
.Попробуйте эти примеры, чтобы увидеть влияние
XACT_ABORT
на значениеXACT_STATE()
:ОБНОВИТЬ
Хотя это и не является частью исходного Вопроса, основываясь на этих комментариях к этому Ответу:
Прежде чем использовать
XACT_ABORT ON
везде, я хотел бы спросить: что именно здесь получается? Я не нашел в этом необходимости и вообще рекомендую использовать его только в случае необходимости. Независимо от того, хотите ли вы,ROLLBACK
можете ли вы обрабатывать его достаточно легко, используя шаблон, показанный в ответе @ Remus , или шаблон, который я использовал годами, по сути одно и то же, но без точки сохранения, как показано в этом ответе (который обрабатывает вложенные вызовы):Должны ли мы обрабатывать транзакции в коде C #, а также в хранимых процедурах
ОБНОВЛЕНИЕ 2
Я провел немного больше тестирования, на этот раз путем создания небольшого консольного приложения .NET, создания транзакции на уровне приложения перед выполнением каких-либо
SqlCommand
объектов (т. Е. Черезusing (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...
), а также с использованием ошибки прерывания пакета вместо простого выражения. - ошибка, и обнаружил, что:@@TRANCOUNT
все еще> 0.COMMIT
поскольку это сгенерирует ошибку, сообщающую, что Транзакция «uncommittable». Вы также не можете игнорировать это / ничего не делать, так как по окончании пакета будет сгенерирована ошибка, в которой будет указано, что пакет завершен с длительной транзакцией, не подлежащей завершению, и он будет откатан (так что, если он все равно будет автоматически откатываться, зачем выкидывать ошибку?). Таким образом, вы должны выдать явныйROLLBACK
, возможно, не в непосредственномCATCH
блоке, а до окончания пакета.TRY...CATCH
конструкции, когдаXACT_ABORT
естьOFF
, ошибки, которые автоматическиTRY
прервали бы Транзакцию, если бы они произошли вне блока, такие как ошибки пакетного прерывания, отменят работу, но не прервут Tranasction, оставив его как «uncommitable». Выдача aROLLBACK
- это скорее формальность, необходимая для закрытия транзакции, но работа уже была отменена.XACT_ABORT
это такON
, большинство ошибок действуют как прерывание партии и, следовательно, ведут себя так, как описано в пуле выше (# 3).XACT_STATE()
По крайней мере вCATCH
блоке будет отображаться-1
ошибка для прерывания пакета, если во время ошибки была активная заявка.XACT_STATE()
иногда возвращается,1
даже если нет активной транзакции. Если@@SPID
(среди прочего) вSELECT
списке наряду сXACT_STATE()
, тоXACT_STATE()
вернет 1, когда нет активной транзакции. Такое поведение началось в SQL Server 2012 и существует в 2014 году, но я не тестировал в 2016 году.С учетом вышесказанного:
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
:ОБНОВЛЕНИЕ 3
Относится к пункту № 6 в разделе ОБНОВЛЕНИЕ 2 (т. Е. Возможное неверное значение, возвращаемое
XACT_STATE()
при отсутствии активной транзакции):XACT_STATE()
не сообщалось об ожидаемых значениях при использовании в триггерах илиINSERT...EXEC
сценариях: xact_state () нельзя надежно использовать, чтобы определить, обречена ли транзакция . Однако в этих 3 -х вариантах (я тестировал только на 2008 R2),XACT_STATE()
это не ошибочно сообщают ,1
когда используется вSELECT
с@@SPID
.Существует ошибка Connect, поданная в связи с упомянутым здесь поведением, но она закрыта как «By Design»: XACT_STATE () может возвращать неверное состояние транзакции в SQL 2012 . Тем не менее, тест был выполнен при выборе из DMV, и был сделан вывод, что для этого, естественно, потребуется сгенерированная системой транзакция, по крайней мере, для некоторых DMV. В последнем ответе MS также было указано, что:
Эти утверждения неверны, учитывая следующий пример:
Следовательно, новая ошибка Connect:
XACT_STATE () возвращает 1 при использовании в SELECT с некоторыми системными переменными, но без предложения FROM
ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ, что в «XACT_STATE () может возвращать неверное состояние транзакции в SQL 2012» элемент Connect, связанный непосредственно выше, Microsoft (ну, представитель) утверждает:
Тем не менее, я не могу найти причин не доверять
@@TRANCOUNT
. Следующий тест показывает, что@@TRANCOUNT
действительно возвращается1
в транзакции автоматической фиксации:Я также проверил на реальной таблице с триггером и
@@TRANCOUNT
в триггере точно отчитался,1
хотя явной транзакции не было запущено.источник
Защитное программирование требует от вас написания кода, который обрабатывает как можно больше известных состояний, тем самым уменьшая вероятность ошибок.
Проверка XACT_STATE () для определения возможности выполнения отката - просто хорошая практика. Слепая попытка отката означает, что вы можете непреднамеренно вызвать ошибку в вашем TRY ... CATCH.
Один из способов отката может быть неудачным внутри TRY ... CATCH, если вы явно не начали транзакцию. Копирование и вставка блоков кода могут легко вызвать это.
источник
ROLLBACK
не сработало бы внутри,CATCH
и вы привели хороший пример. Я предполагаю, что это может также быстро стать грязным, если вложенные транзакции и вложенные хранимые процедуры с их собственнымиTRY ... CATCH ... ROLLBACK
участвуют.IF (XACT_STATE()) = 1 COMMIT TRANSACTION;
Как мы можем оказаться внутриCATCH
блока с коммитируемой транзакцией? Я бы не посмел совершить какой-то (возможный) мусор изнутриCATCH
. Я рассуждаю так: если мы внутри, что-CATCH
то пошло не так, я не могу доверять состоянию базы данных, поэтому я бы улучшилROLLBACK
все, что у нас есть.