ПрограммированиеSQL-разработчик

Как реализовать надёжную обработку исключительных ситуаций (Exception Handling) и отката транзакций в хранимых процедурах SQL, особенно в сценарии вложенных (Nested) транзакций? Какие ограничения и особенности у таких решений?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Исторически обработка ошибок в SQL была ограничена только откатом транзакции после фатальной ошибки. Однако с развитием бизнес-логики всё чаще требуется точное логирование ошибки, откат только тех изменений, что были произведены, а также возможность вложенности вызовов процедур (nested transactions).

Проблема заключается в том, что не во всех СУБД вложенные транзакции поддерживаются полностью (например, в MS SQL Server это скорее уровни сохранённых точек SAVEPOINT, а не настоящие транзакции), а исключения при ошибках могут прерывать выполнение до необходимой очистки, если механизмы TRY/CATCH или аналоги не используются.

Решение: использовать конструкции TRY/CATCH (или аналогичные), SAVEPOINT для вложенности, а также настраивать поведение процедур при ошибках (например, SET XACT_ABORT в SQL Server).

Пример кода (MS SQL Server):

BEGIN TRY BEGIN TRANSACTION -- операции EXEC dbo.InnerProcedure COMMIT TRANSACTION END TRY BEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION -- логирование ошибки INSERT INTO error_log VALUES (ERROR_NUMBER(), ERROR_MESSAGE(), GETDATE()) END CATCH

Ключевые особенности:

  • Использование TRY/CATCH для перехвата ошибок.
  • Контроль @@TRANCOUNT для вложенности.
  • SAVEPOINT (или собственные аналоги для ручного контроля возврата к точке).

Вопросы с подвохом.

При возникновении ошибки во вложенной транзакции происходит ли реальный откат всех изменений?

В большинстве СУБД вложенные транзакции — это не отдельные транзакции, а точки возврата (SAVEPOINT). При общем откате откатываются все изменения вплоть до начальной (outer) транзакции.

Что произойдёт, если забыть проверить @@TRANCOUNT и выполнить ROLLBACK вне активной транзакции?

Будет вызвана ошибка — нет активной транзакции для отката. Всегда проверяйте @@TRANCOUNT > 0 перед ROLLBACK.

Работают ли триггеры и курсоры в TRY/CATCH?

Да, но ошибки в триггере могут вызвать переход в CATCH и откат всей транзакции. Необходимо предусматривать обработку ошибок как в теле процедуры, так и в вызываемых объектах (процедурах, триггерах).

Типовые ошибки и анти-паттерны

  • Отсутствие проверки @@TRANCOUNT — попытка отката неактивной транзакции.
  • Лишние COMMIT или ROLLBACK во вложенных уровнях.
  • Логирование ошибки без сохранения всех деталей (номер, текст, параметры).

Пример из жизни

Негативный кейс

Несколько вложенных процедур, каждая использует BEGIN TRANSACTION / COMMIT без TRY/CATCH, при ошибке часть данных изменена, часть нет.

Плюсы:

  • Простой код

Минусы:

  • Рассинхронизация состояния
  • Неочевидные ошибки после сбоя

Позитивный кейс

Вся логика завернута в TRY/CATCH, вложенные процедуры используют SAVEPOINT или оперируют внутри одной внешней транзакции, все ошибки логируются.

Плюсы:

  • Полная целостность
  • Аудит ошибок

Минусы:

  • Более громоздкий скрипт
  • Чуть сложнее сопровождение логики автосохранения.