Исторически обработка ошибок в 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
Ключевые особенности:
При возникновении ошибки во вложенной транзакции происходит ли реальный откат всех изменений?
В большинстве СУБД вложенные транзакции — это не отдельные транзакции, а точки возврата (SAVEPOINT). При общем откате откатываются все изменения вплоть до начальной (outer) транзакции.
Что произойдёт, если забыть проверить @@TRANCOUNT и выполнить ROLLBACK вне активной транзакции?
Будет вызвана ошибка — нет активной транзакции для отката. Всегда проверяйте @@TRANCOUNT > 0 перед ROLLBACK.
Работают ли триггеры и курсоры в TRY/CATCH?
Да, но ошибки в триггере могут вызвать переход в CATCH и откат всей транзакции. Необходимо предусматривать обработку ошибок как в теле процедуры, так и в вызываемых объектах (процедурах, триггерах).
Несколько вложенных процедур, каждая использует BEGIN TRANSACTION / COMMIT без TRY/CATCH, при ошибке часть данных изменена, часть нет.
Плюсы:
Минусы:
Вся логика завернута в TRY/CATCH, вложенные процедуры используют SAVEPOINT или оперируют внутри одной внешней транзакции, все ошибки логируются.
Плюсы:
Минусы: