ПрограммированиеSQL инженер

Как реализовать обработку ошибок и транзакций в хранимых процедурах на SQL для обеспечения целостности бизнес-логики? В чем отличие подходов в разных СУБД?

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

Ответ.

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

Проблема
Не все СУБД по умолчанию обеспечивают откат при ошибке в многошаговых процедурах (особенно если используются TRY/CATCH, EXCEPTION-обработчики). Неправильное написание транзакционной логики приводит к «кусочным» изменениям (часть данных обновилась, часть — нет), что компрометирует бизнес-целостность.

Решение
Для корректной обработки используются явное открытие и завершение транзакций (BEGIN TRANSACTION, COMMIT/ROLLBACK) с отловом ошибок. В разных СУБД синтаксис и возможности различаются.

Пример для MS SQL Server:

CREATE PROCEDURE update_balances(@from INT, @to INT, @amount DECIMAL(10,2)) AS BEGIN BEGIN TRY BEGIN TRANSACTION; UPDATE accounts SET balance = balance - @amount WHERE id = @from; UPDATE accounts SET balance = balance + @amount WHERE id = @to; COMMIT TRANSACTION; END TRY BEGIN CATCH ROLLBACK TRANSACTION; INSERT INTO error_log(err_message, err_date) VALUES(ERROR_MESSAGE(), GETDATE()); THROW; -- пробрасываем ошибку дальше END CATCH END;

Для PostgreSQL:

CREATE OR REPLACE FUNCTION update_balances(from_id INT, to_id INT, amount NUMERIC) RETURNS void AS $$ BEGIN UPDATE accounts SET balance = balance - amount WHERE id = from_id; UPDATE accounts SET balance = balance + amount WHERE id = to_id; -- Исключение будет автоматически откатывать транзакцию EXCEPTION WHEN OTHERS THEN INSERT INTO error_log(err_message, err_date) VALUES(SQLERRM, NOW()); RAISE; END; $$ LANGUAGE plpgsql;

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

  • Открываем и явно завершаем транзакцию.
  • Обрабатываем ошибки в catch/exception-блоке и откатываем изменения.
  • Фиксируем информацию об ошибках для аудита.

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

Может ли ошибка внутри хранимой процедуры всегда автоматически откатывать transaction? Нет! Например, в MS SQL Server такой откат не гарантирован — требуется явный ROLLBACK. В PostgreSQL при любой ошибке вся транзакция становится "испорченной" и требует завершения.

Можно ли "частично" закоммитить внутри одной процедуры? Плохая практика. Рекомендуется делать commit/rollback только один раз в конце бизнес-операции. Часть платформ позволяет "savepoints", но это для спецзадач.

Какие типы ошибок не поддаются ловле через TRY/CATCH/EXCEPTION в SQL? Некоторые системные сбои (например, разрыв соединения с сервером) не попадут в обработчик и могут сделать откат невозможным.

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

  • Оставлять COMMIT без обработки ошибок: фиксируем в базе "битые" или частично измененные данные
  • Множественные COMMIT/ROLLBACK внутри одной бизнес-операции
  • Игнорирование записи ошибок

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

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

Хранимая процедура обновляла три таблицы и завершалась COMMIT без отлова ошибок. В результате при ошибке во второй таблице первая уже была изменена, и пришлось часами "откатывать" данные вручную из бекапов.

Плюсы:
Просто и "работает", пока не случится сбой.

Минусы:
Риск неконсистентности; невозможно быстро восстановиться при ошибках.

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

Внедрили явную обработку ошибок + rollback транзакций с записью всех сбоев в отдельный log. Возврат к целостному состоянию базы занимает секунды, сбои анализируются и минимизируются.

Плюсы:
Гарантия неизменности мытых данных даже при сбоях; прозрачная логика.

Минусы:
Слегка сложнее для начинающих, требует дисциплины в коде.