Фиксация данных в БД при обработке исключений
При разработке бизнес-логики на сервере БД PostgreSQL, иногда возникает потребность в фиксации данных, как предшествующих исключительной ситуации, так и возникших в результате появления исключительной ситуации.
Рассмотрим простой пример. В БД реализована длинная и сложная хранимая процедура, например, ежемесячное закрытие отчетного периода (или сложный расчёт, или импорт пользовательских данных, или обмен с другими серверами). Важные события хотелось бы видеть в таблице логов. Можно, конечно, просто писать в нее без всяких ухищрений. Но есть один серьезный недостаток — если в процедуре происходит необработанная ошибка — транзакция откатывает не только таблицы прикладных данных, но и таблицы логов.
Для решения задачи, можно воспользоваться механизмом dblink, который откроет новое подключение к той же БД в которой выполняется процедура, и осуществит сохранение данных в отдельной транзакции, независимой от текущей. В таком случае, возбуждение исключительной ситуации в коде основной процедуры не приведет к откату сохраненных таким способом данных.
drop table if exists test.t_error;
drop table if exists test.t_log;
create table if not exists test.t_error(i bigint, name varchar);
create table if not exists test.t_log(id bigint);
CREATE or replace PROCEDURE test.exception_log_test()
LANGUAGE plpgsql
AS $$
DECLARE
i bigint;
j float;
begin
-- установка persistent-соединения, чтобы внутри цикла уже просто обращаться к нему не переоткрывая заново
-- соединение устанавливается с той же самой БД, в которой работает текущая процедура
perform dblink_connect('myconn', 'dbname=' || current_database());
-- "длинный" цикл
for i in 1..3 loop
begin
-- логгирование всего подряд, и оно не откатится
perform dblink_exec('myconn', 'insert into test.t_log (id) values('||i::text||');');
-- "сложный расчет", иногда выбрасывающий исключения
j=1.0/(2-i);
-- обработка исключения (если требуется)
exception when others then
-- логгирование только отловленных ошибок
insert into test.t_error(i, name) select i, SQLERRM;
end;
end loop;
-- корректное закрытие соединения
perform dblink_disconnect('myconn');
END;
$$;
call test.exception_log_test();
select * from test.t_log;
id|
--+
1|
2|
3|
select * from test.t_error;
i|name |
-+---------------+
2|деление на ноль|
В результате выполнения видно, что в таблицу test.t_log накопился полный журнал событий, а в таблицу test.t_error только те записи, которые привели к ошибке.