SQL Server - транзакции откатываются при ошибке?

193

У нас есть клиентское приложение, которое выполняет SQL на SQL Server 2005, например:

BEGIN TRAN;
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
COMMIT TRAN;

Отправляется одной длинной строковой командой.

В случае сбоя одной из вставок или сбоя какой-либо части команды SQL Server откатывает транзакцию? Если он не откатывается, нужно ли отправлять вторую команду для его отката?

Я могу дать конкретную информацию о API и языке, который я использую, но я думаю, что SQL Server должен отвечать одинаково для любого языка.

jonathanpeppers
источник

Ответы:

205

Вы можете поставить set xact_abort onперед своей транзакцией, чтобы убедиться, что sql автоматически откатывается в случае ошибки.

Грег Б
источник
1
Будет ли это работать на MS SQL 2K и выше? Это кажется самым простым решением.
Джонатанпеперс
1
Он указан в документах на 2000, 2005 и 2008 годы, поэтому я предполагаю, что да. Мы используем его в 2008 году.
8
Нужно ли его выключать или это за сеанс?
Марк
5
@Marc область действия xact_abortнаходится на уровне соединения.
Кит
2
@AlexMcMillan Оператор DROP PROCEDURE изменяет структуру базы данных, в отличие от INSERT, который работает только с данными. Поэтому он не может быть включен в транзакцию. Я упрощаю, но в основном так оно и есть.
ексорцо
195

Вы правы в том, что вся транзакция будет откатана. Вы должны выполнить команду, чтобы откатить его.

Вы можете обернуть это в TRY CATCHблок следующим образом

BEGIN TRY
    BEGIN TRANSACTION

        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);
        INSERT INTO myTable (myColumns ...) VALUES (myValues ...);

    COMMIT TRAN -- Transaction Success!
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN --RollBack in case of Error

    -- you can Raise ERROR with RAISEERROR() Statement including the details of the exception
    RAISERROR(ERROR_MESSAGE(), ERROR_SEVERITY(), 1)
END CATCH
Радж Море
источник
2
Мне больше нравится решение DyingCactus, его нужно изменить на 1 строку кода. Если ваш, если по какой-то причине лучше (или более надежно), дайте мне знать.
Джонатанпеперс
14
Try catch дает вам возможность перехватить (и, возможно, исправить) ошибку и при необходимости вызвать специальное сообщение об ошибке.
Радж Море
11
Я думаю, что «захват и запись» чаще, чем «захват и исправление».
quillbreaker
24
Синтаксис RAISERROR является неправильным, по крайней мере, в SQL Server 2008R2 и более поздних версиях. См. Msdn.microsoft.com/en-us/library/ms178592.aspx для правильного синтаксиса.
Эрик Дж
2
@BornToCode Чтобы убедиться, что транзакция существует. Допустим, вы откатили свою транзакцию при заданном условии (в try), но после этого код завершился ошибкой. Там нет больше транзакции, но вы все еще собираетесь в catch.
Габриэль GM
42

Вот код с получением сообщения об ошибке при работе с MSSQL Server 2016:

BEGIN TRY
    BEGIN TRANSACTION 
        -- Do your stuff that might fail here
    COMMIT
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0
        ROLLBACK TRAN

        DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
        DECLARE @ErrorSeverity INT = ERROR_SEVERITY()
        DECLARE @ErrorState INT = ERROR_STATE()

    -- Use RAISERROR inside the CATCH block to return error  
    -- information about the original error that caused  
    -- execution to jump to the CATCH block.  
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
Samwise
источник
1
Мне пришлось использовать DECLARE @Var TYPE; SET @Var = ERROR;для повышения ошибок в SQL Server 2005. В противном случае приведенный выше код для повышения ошибок работает и для старых БД. Попытка присвоить значение по умолчанию локальной переменной - вот что вызывало проблему.
Jtlindsey
Вы можете использовать простой THROW; вместо объявлений RAISERROR и ERROR_ *.
родзмкий
21

Из статьи MDSN « Управление транзакциями ( компонент Database Engine)» .

Если в пакете возникает ошибка оператора времени выполнения (например, нарушение ограничения), по умолчанию в компоненте Database Engine выполняется откат только оператора, который сгенерировал ошибку. Вы можете изменить это поведение, используя инструкцию SET XACT_ABORT. После выполнения команды SET XACT_ABORT ON любая ошибка оператора во время выполнения вызывает автоматический откат текущей транзакции. SET XACT_ABORT не влияет на ошибки компиляции, такие как синтаксические ошибки. Для получения дополнительной информации см. SET XACT_ABORT (Transact-SQL).

В вашем случае он откатит всю транзакцию, если какая-либо из вставок потерпит неудачу.

Виталий
источник
3
что нам нужно для обработки синтаксических ошибок? или ошибки компиляции? если кто-нибудь из них случится, вся транзакция должна быть откатана
MonsterMMORPG
Захват ошибок компиляции / синтаксиса - это то, для чего предназначены проекты SSDT. :-)
Joe the Coder
10

В случае сбоя одной из вставок или сбоя какой-либо части команды сервер SQL откатывает транзакцию?

Нет.

Если он не откатывается, нужно ли отправлять вторую команду для его отката?

Конечно, вы должны выдать ROLLBACKвместо COMMIT.

Если вы хотите решить, следует ли совершать или откатывать транзакцию, вы должны удалить COMMITпредложение из оператора, проверить результаты вставок, а затем выполнить один COMMITили в ROLLBACKзависимости от результатов проверки.

Quassnoi
источник
Так что, если я получаю сообщение об ошибке, скажем «конфликт первичных ключей», мне нужно отправить второй вызов для отката? Я думаю, это имеет смысл. Что произойдет, если возникнет ошибка, связанная с сетью, например, разрыв соединения во время очень длительного выполнения оператора SQL?
Джонатанпеперс
2
Когда время соединения истекает, базовый сетевой протокол (например, Named Pipesили TCP) разрывает соединение. Когда соединение разрывается, SQL Serverостанавливает все запущенные в данный момент команды и откатывает транзакцию.
Quassnoi
1
Так что решение DyingCactus выглядит так, как будто оно решает мою проблему, спасибо за помощь.
Джонатанпеперс
Если необходимо прервать на любой ошибке, то да, это самый лучший вариант.
Quassnoi