Учитывая только код, показанный в вопросе, и предполагая, что ни один из трех подпроцессов не имеет какой-либо явной обработки транзакций, тогда да, ошибка в любом из трех подпроцессов будет обнаружена, и блок 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}
.
- Если первой транзакции было присвоено имя a) и b), для которой были
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 Query 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 Query 1»). @@TRANCOUNT
сейчас 0.
COMMIT
один раз: @@TRANCOUNT
снижается до 1.
COMMIT
один раз, а затем ROLLBACK TRAN B
один раз: @@TRANCOUNT
уменьшается до 1. Затем он отменяет «DML Query 4» (доказывая, что COMMIT ничего не делал). @@TRANCOUNT
все еще 1.
Имена транзакций и имена точек сохранения:
- может содержать до 32 символов
- обрабатываются как имеющие двоичное сопоставление (без учета регистра, как указано в документации), независимо от сопоставлений на уровне экземпляра или на уровне базы данных.
- Подробности смотрите в разделе « Имена транзакций » следующего поста: « Что в имени ?: Внутри странного мира идентификаторов T-SQL»
Хранимая процедура сама по себе не является неявной транзакцией. Каждый запрос, если явная транзакция не была запущена, является неявной транзакцией. Вот почему явные транзакции вокруг отдельных запросов не нужны, если только для этого не может быть программной причины, в ROLLBACK
противном случае любая ошибка в запросе является автоматическим откатом этого запроса.
При вызове хранимой процедуры она должна завершиться со значением @@TRANCOUNT
, совпадающим с тем, когда она была вызвана. Это означает, что вы не можете:
- Запустите
BEGIN TRAN
в proc без фиксации, ожидая фиксации в вызывающем / родительском процессе.
- Вы не можете выдать,
ROLLBACK
если явная транзакция была запущена до вызова процедуры, так как она вернется @@TRANCOUNT
к 0.
Если вы выйдете из хранимой процедуры с количеством транзакций, которое будет выше или ниже, чем при просмотре, вы получите ошибку, похожую на:
Сообщение 266, Уровень 16, Состояние 2, Процедура YourProcName, Строка 0
Количество транзакций после EXECUTE указывает на несовпадающее количество операторов BEGIN и COMMIT. Предыдущий счет = X, текущий счет = Y.
Табличные переменные, как и обычные переменные, не связаны транзакциями.
Что касается обработки транзакций в процессах, которые могут вызываться независимо (и, следовательно, требовать обработки транзакций) или вызываться из других процедур (следовательно, не требуется обработка транзакций): это может быть выполнено несколькими различными способами.
То, как я справляюсь с этим уже несколько лет, похоже, работает хорошо, только BEGIN
/ COMMIT
/ ROLLBACK
на самом внешнем слое. Вызовы sub-proc просто пропускают команды транзакции. Ниже я изложил, что я вкладываю в каждый процесс (ну, каждый, который нуждается в обработке транзакций).
- В верхней части каждого процесса,
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 # Code, а также в хранимых процедурах .
Выходя за рамки «основ», необходимо учитывать некоторые дополнительные нюансы транзакций:
По умолчанию транзакции в большинстве случаев автоматически не откатываются / отменяются при возникновении ошибки. Обычно это не проблема, если вы правильно обрабатываете ошибки и звоните 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;
.
Внутри Trigger XACT_ABORT
неявно установлено значение ON
. Это вызывает любую ошибку в Trigger, чтобы отменить весь оператор DML, который запустил Trigger.
У вас всегда должна быть правильная обработка ошибок, особенно при использовании транзакций. 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
, Транзакция будет в новом состоянии, «uncommitable». В этом состоянии вы не можете и не 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 установлено значение ON? , Обратите внимание, что существует небольшая ошибка, XACT_STATE()
которая приводит к ложному возвращению 1
в некоторых сценариях: XACT_STATE () возвращает 1, когда используется в SELECT с некоторыми системными переменными, но без предложения FROM
Примечания об оригинальном коде:
- Вы можете удалить имя, данное транзакции, поскольку оно не помогает никому.
- Вам не нужно
BEGIN
и END
вокруг каждого EXEC
звонка
spNewBilling3
выдает ошибку, но вы не хотите откатspNewBilling2
илиspNewBilling1
, то просто удалите[begin|rollback|commit] transaction createSavebillinginvoice
изspSavesomename
.