Я разработчик C #, изучаю TSQL. Я написал такой сценарий:

begin transaction
--Insert into several tables
end transaction

Но мне сказали, что это не очень хорошая идея, и использовать что-то вроде этого:

BEGIN TRANSACTION;

BEGIN TRY
    -- Generate a constraint violation error.
    DELETE FROM Production.Product
    WHERE ProductID = 980;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;

    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;
END CATCH;

IF @@TRANCOUNT > 0
    COMMIT TRANSACTION;
GO

Не понимаю, почему второй пример более правильный. Будет ли первый работать иначе? Кажется, первый либо обновит все таблицы, либо вообще не обновит? Я не понимаю, почему необходима проверка @@TRANCOUNT перед фиксацией.

8
Greg Gum 6 Май 2016 в 17:51

3 ответа

Лучший ответ

Открывайте транзакцию только тогда, когда вы находитесь внутри блока try и непосредственно перед фактическим оператором. И сразу же фиксируйте его, не дожидаясь, пока ваш элемент управления перейдет в конец пакета, чтобы зафиксировать ваши транзакции.

После того, как вы находитесь в блоке Try и открыли транзакцию, если что-то пойдет не так, элемент управления перейдет к блоку CATCH. Просто откатите свою транзакцию там и выполните другую обработку ошибок по мере необходимости.

Я добавил небольшую проверку, прежде чем на самом деле откатить проверку транзакции для любой открытой транзакции с помощью функции @@ ROWCOUNT. В этом сценарии это не имеет большого значения. Это более полезно, когда вы выполняете некоторые проверки валидации в своем блоке try, прежде чем открывать транзакцию, например, проверять значения параметров и другие вещи и вызывать ошибку в блоке try, если какая-либо из проверок валидации не удалась.В этом случае управление перейдет к блоку catch даже не открывая там транзакцию, вы можете проверить наличие открытых транзакций и выполнить откат, если есть открытые. В вашем случае, как есть, вам действительно не нужно проверять открытую транзакцию, так как вы не войдете в блок catch, если внутри транзакции что-то не пойдет не так.

BEGIN TRY

  BEGIN TRANSACTION 
     -- Multiple Inserts
    INSERT INTO....
    INSERT INTO.... 
    INSERT INTO.... 
 COMMIT TRANSACTION 
    PRINT 'Rows inserted successfully...'

END TRY

BEGIN CATCH 
  IF (@@TRANCOUNT > 0)
   BEGIN
      ROLLBACK TRANSACTION 
      PRINT 'Error detected, all changes reversed'
   END 
    SELECT
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_SEVERITY() AS ErrorSeverity,
        ERROR_STATE() AS ErrorState,
        ERROR_PROCEDURE() AS ErrorProcedure,
        ERROR_LINE() AS ErrorLine,
        ERROR_MESSAGE() AS ErrorMessage
END CATCH
4
M.Ali 6 Май 2016 в 14:57

Я в двух мыслях по поводу TRY ... CATCH in T-SQL.

Хотя это потенциально полезное дополнение к языку, тот факт, что он доступен, не всегда является причиной его использования.

Принимая ваш

DELETE FROM Table WHERE...

Пример (который, как я понимаю, является только примером). Единственный способ вывести из строя ошибку - это если код серьезно испортился из-за схемы. (например, если кто-то создает внешний ключ с таблицей на конце PK).

При надлежащем тестировании такое несоответствие между кодом и схемой никогда не должно попадать в производство. Предполагая, что это так, ИМХО «грубое», неопосредованное сообщение об ошибке, которое будет в результате, является лучшим индикатором того, что пошло не так, чем «вежливое» обертывание его в оператор SELECT для возврата к клиенту. (Что может быть равносильно упомянутому SeanLange антипаттерну try / squelch).

Для более сложных сценариев я вижу использование TRY ... CATCH. Хотя, IMHO, это не замена тщательной проверки входных параметров.

0
SebTHU 6 Май 2016 в 15:11

Я хочу изложить здесь свою точку зрения как разработчика C #:

В приведенном выше простом сценарии (просто вставка в несколько таблиц из сценария) нет причин для добавления попытки / улова, так как это не добавляет транзакции преимуществ. Оба примера приведут к одинаковому результату: либо будут вставлены все таблицы, либо их не будет. Состояние базы данных остается неизменным. (Поскольку COMMIT TRANSACTION никогда не вызывается, откат неявно вызывается сервером Sql в конце скрипта.)

Однако бывают случаи, когда в try / catch можно делать то, что невозможно с интегрированной обработкой ошибок. Например, запись ошибки в таблицу ошибок.

По моему опыту работы с C #, единственный раз, когда я могу использовать Try / Catch, - это когда есть вещи, которые находятся вне контроля разработчика, например, попытка открыть файл. В таком случае единственный способ управлять исключением, сгенерированным фреймворком .Net, - это использовать Try / Catch.

Если бы я выполнял хранимую процедуру и хотел бы вручную проверить состояние данных и вручную вызвать ROLLBACK TRANSACTION, я бы это увидел. Но это все равно не потребует попытки / улова.

1
Greg Gum 6 Май 2016 в 17:38