SQL Server - транзакції повертаються помилково?


193

У нас є клієнтська програма, яка працює на 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 повинен відповідати однаково для будь-якої мови.


Відповіді:


205

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


1
Чи буде це працювати в MS SQL 2K і вище? Це здається найпростішим рішенням.
jonathanpeppers

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 рядок коду. Якщо ваше, якщо з якихось причин краще (або надійніше), дайте мені знати.
jonathanpeppers

13
Спроба улову дає вам можливість захопити (і, можливо, виправити) помилку та підняти спеціальне повідомлення про помилку, якщо потрібно.
Raj More

11
"Знімайте та записуйте" частіше, ніж "захоплення та виправлення", я думаю.
quillbreaker

24
Синтаксис RAISERROR є невірним принаймні в SQL Server 2008R2 та пізніших версіях. Див. Msdn.microsoft.com/en-us/library/ms178592.aspx для правильного синтаксису.
Ерік Дж.

2
@BornToCode Щоб переконатися, що транзакція існує .. Скажімо, ви скасували свою транзакцію за певної умови (в try), але код виходить з ладу після. Більше немає транзакцій, але ви все одно переходите до catch.
Габріель Г.М.

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

1
Мені довелося використовувати DECLARE @Var TYPE; SET @Var = ERROR;для помилок підвищення на сервері sql 2005. Інакше наведений вище код для підвищення помилок працює і для старих БД. Спроба призначити значення за замовчуванням для локальної змінної - це те, що викликало проблему.
jtlindsey

Ви можете скористатися простим КРОКУ; замість декларацій RAISERROR та ERROR_ *.
rodzmkii

21

З статті MDSN, Управління транзакціями (двигун бази даних) .

Якщо помилка оператора виконання (наприклад, порушення обмеження) трапляється в пакеті, поведінка за замовчуванням в Механіці баз даних полягає в тому, щоб повернути лише те твердження, яке генерувало помилку. Ви можете змінити цю поведінку за допомогою оператора SET XACT_ABORT. Після того, як SET XACT_ABORT ON буде виконаний, будь-яка помилка заяви оператора викликає автоматичний відкат поточної транзакції. SET XACT_ABORT не впливає на помилки компіляції, такі як синтаксичні помилки. Для отримання додаткової інформації див. SET XACT_ABORT (Transact-SQL).

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


3
що нам потрібно для обробки синтаксичних помилок? чи компілювати помилки? якщо хтось із них трапиться, цілу транзакцію слід
відкрутити

Локальні помилки компіляції / синтаксису - це те, для чого призначені проекти SSDT. :-)
Джо Кодер

10

Якщо одна з вставок не працює, або якась частина команди не вдається, чи SQL-сервер повертає транзакцію?

Ні, це не є.

Якщо воно не відкидається, чи потрібно мені надіслати другу команду, щоб повернути його назад?

Звичайно, ви повинні видавати ROLLBACKзамість COMMIT.

Якщо ви хочете вирішити, чи здійснювати чи відмовляти транзакцію, вам слід видалити COMMITречення із заяви, перевірити результати вставок, а потім видати COMMITабо ROLLBACKзалежно від результатів перевірки.


Отже, якщо я отримав помилку, скажіть "Конфлікт первинного ключа", мені потрібно надіслати другий дзвінок для відкату? Я думаю, це має сенс. Що станеться, якщо помилка, пов’язана з мережею, наприклад, з'єднання розривається під час дуже тривалого оператора SQL?
jonathanpeppers

2
Коли час з’єднання припиняється, базовий мережевий протокол (наприклад, Named Pipesабо TCP) розриває з'єднання. Коли з'єднання розірвано, SQL Serverзупиняє всі поточно запущені команди та відкочує транзакцію.
Quassnoi

1
Тож рішення DyingCactus виглядає так, що воно вирішує мою проблему, дякую за допомогу.
jonathanpeppers

Якщо вам потрібно скасувати будь-яку помилку, то так, це найкращий варіант.
Quassnoi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.