PL/pgSQL
Обработка ошибок
16
Авторские права
© Postgres Professional, 2017–2024
Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк
Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Обработка ошибок в блоке PL/pgSQL
Имена и коды ошибок
Как происходит поиск обработчика
Накладные расходы на обработку ошибок
3
Обработка ошибок в блоке
Обработка выполняется, если есть секция EXCEPTION
Выполняется откат к точке сохранения в начале блока
точка сохранения устанавливается неявно, если в блоке есть
секция EXCEPTION
Если имеется обработчик, соответствующий ошибке
выполняются команды обработчика
блок завершается успешно
Если нет подходящего обработчика
блок завершается ошибкой
Если внутри блока кода происходит ошибка времени выполнения,
то обычно программа (блок, функция или процедура) аварийно
прерывается, а текущая транзакция переходит в состояние сбоя:
ее нельзя зафиксировать и можно только откатить.
Но ошибку можно перехватить и обработать. Для этого в блоке можно
указать дополнительную секцию EXCEPTION, внутри которой
перечислить условия, соответствующие ошибке, и операторы для
обработки каждой такой ситуации.
EXCEPTION в целом соответствует конструкции try-catch в некоторых
языках программирования (за исключением особенностей, связанных
с обработкой транзакций, конечно).
В начале каждого блока с секцией EXCEPTION неявно
устанавливается точка сохранения. Перед обработкой ошибки
происходит откат к этой точке, в результате отменяются сделанные в
блоке изменения, а также снимаются установленные в блоке
блокировки.
Из-за наличия точки сохранения в блоках процедур с EXCEPTION
запрещено использовать команды COMMIT и ROLLBACK. Зато, хотя
команды SAVEPOINT и ROLLBACK TO SAVEPOINT не работают в
PL/pgSQL, точку сохранения и откат к ней все-таки можно использовать
и в процедурах, и в функциях — неявно.
5
Имена и коды ошибок
Информация об ошибке
имя
пятисимвольный код
дополнительные сведения: сообщение, детальное сообщение, подсказка,
имена объектов, связанных с ошибкой
Двухуровневая иерархия
XX001 – data_corrupted
XX002 – index_corrupted
XX000 – internal_error
P0001 – raise_exception
P0003 – too_many_rows
P0002 – no_data_found
P0004 – assert_failure
P0000 – plpgsql_error
Каждой возможной ошибке соответствует имя и код (строка из пяти
символов). В условии WHEN можно указывать как имя, так и код.
Ошибки организованы в своеобразную двухуровневую иерархию.
Категория ошибки имеет код, заканчивающийся на три нуля, и
соответствует любой ошибке с теми же первыми двумя символами.
Например, код 23000 соответствует категории, которая включает все
ошибки, связанные с нарушением ограничений целостности акие, как
23502 — нарушение ограничения NOT NULL или 23505 — нарушение
уникальности).
Таким образом, кроме обычных ошибок можно использовать имя или
код категории. Можно также использовать специальное имя OTHERS
для того, чтобы перехватить любую ошибку (кроме самых фатальных).
Кроме имени и кода любая ошибка может иметь дополнительные
сведения, облегчающие ее диагностику: сообщение, детальное
сообщение, подсказку.
Все ошибки описаны в приложении A документации:
Ошибки можно не только перехватывать, но и программно вызывать.
7
Поиск обработчика
Необработанная ошибка «поднимается» на уровень выше
в объемлющий блок PL/pgSQL, если он есть
в вызывающую подпрограмму, если она есть
Поиск обработчика определяется стеком вызовов
то есть не определен статически, а зависит от выполнения программы
Никем не обработанная ошибка отправляется клиенту
транзакция переходит в состояние сбоя, клиент должен ее откатить
информация об ошибке записывается в журнал сообщений сервера
Если ни одно из условий, перечисленных в секции EXCEPTION,
не сработает, то ошибка «поднимается» на уровень выше.
Если ошибка возникла в блоке, вложенном в другой блок, то сервер
будет пытаться найти обработчик в объемлющем блоке. Если и там
не найдется подходящего обработчика, весь объемлющий блок будет
считаться ошибочным и ошибка поднимется еще выше, и так далее.
Если мы перебрали все объемлющие блоки и не обнаружили
подходящего обработчика возникшей ошибки, ошибка «поднимается»
на уровень той подпрограммы, которая вызвала наш блок. То есть,
чтобы разобраться, в каком порядке будут просматриваться
обработчики ошибки, надо проанализировать стек вызовов.
Если ни один из возможных обработчиков ошибки не сработает:
сообщение об ошибке обычно попадает в журнал сообщений
сервера (это зависит от настроек сервера, см. тему «PL/pgSQL.
Отладка»);
об ошибке сообщается клиенту, который инициировал вызов кода
в базе данных. Клиент ставится перед фактом: транзакция
переходит в состояние сбоя, и ее можно только откатить.
Как именно клиент будет обрабатывать ошибку, зависит от него самого.
Например, psql выведет сообщение об ошибке и всю доступную
диагностическую информацию. Клиент, ориентированный на конечного
пользователя, может вывести знаменитое «обратитесь к системному
администратору» и т. д.
9
Накладные расходы
Любой блок с секцией EXCEPTION выполняется медленнее
из-за установки неявной точки сохранения
Дополнительные расходы при возникновении ошибки
из-за отката к точке сохранения
Обработку ошибок можно и нужно использовать,
но не стоит употреблять без необходимости
PL/pgSQL и так интерпретируется и использует SQL для вычисления
выражений
для многих задач эта скорость более чем удовлетворительна
проблемы с производительностью обычно связаны с запросами,
а не с кодом PL/pgSQL
Одно только указание секции EXCEPTION уже влечет за собой
накладные расходы из-за того, что в начале блока будет неявно
устанавливаться точка сохранения. А если ошибка действительно
возникает, то накладные расходы еще более увеличиваются из-за того,
что выполняется откат транзакции к точке сохранения.
Поэтому если есть простой способ обойтись без обработки
исключений, лучше от нее отказаться. Не стоит строить логику работы
программы на «жонглировании» исключениями.
Однако если обработка ошибок действительно нужна, не стоит
сомневатьсяобрабатывать ошибки можно и нужно несмотря
на накладные расходы.
Во-первых, язык PL/pgSQL довольно медленный сам по себе из-за
интерпретации и обращений к SQL для вычисления выражений.
Во-вторых, очень часто эта скорость вполне удовлетворительна.
Да, можно и быстрее, если написать на C, но зачем?
И в-третьих, основные проблемы с производительностью, как правило,
связаны со скоростью выполнения запросов из-за неправильно
построенных планов, а вовсе не со скоростью работы процедурного
кода (см. курс QPT «Оптимизация запросов»).
Так что если есть альтернативное решение, которое и проще, и
быстрее — конечно лучше воспользоваться им.
11
Итоги
Поиск обработчика ошибки происходит «изнутри наружу»
в порядке вложенности блоков и вызова подпрограмм
В начале блока с EXCEPTION устанавливается неявная точка
сохранения, при ошибке происходит откат к этой точке
Неперехваченная ошибка приводит к обрыву транзакции,
сообщения отправляются клиенту и в журнал сервера
Обработка ошибок связана с накладными расходами
12
Практика
1. Если при добавлении новой книги указать одного и того же
автора несколько раз, произойдет ошибка.
Измените функцию add_book: перехватите ошибку
нарушения уникальности и вместо нее вызовите ошибку
с понятным текстовым сообщением.
Проверьте изменения в приложении.
1. Чтобы определить название ошибки, которую необходимо
перехватить, перехватите все ошибки (WHEN OTHERS) и выведите
необходимую информацию (вызвав новую ошибку с соответствующим
текстом).
После этого не забудьте заменить WHEN OTHERS на конкретную
ошибку — пусть остальные типы ошибок обрабатываются на более
высоком уровне, раз в этом месте кода нет возможности сделать что-то
конструктивное.
(В реальной жизни не стоило бы обрабатывать и нарушение
уникальности — лучше было бы изменить приложение так, чтобы оно
не позволяло указывать двух одинаковых авторов.)
13
Практика+
1. Ряд языков имеет конструкцию try … catch … finally …,
в которой try соответствует BEGIN, catch — EXCEPTION,
а операторы из блока finally срабатывают всегда, независимо
от того, возникло ли исключение и было ли оно обработано
блоком catch. Предложите способ добиться подобного
эффекта в PL/pgSQL.
2. Сравните стеки вызовов, получаемые конструкциями GET
STACKED DIAGNOSTICS с элементом pg_exception_context
и GET [CURRENT] DIAGNOSTICS с элементом pg_context.
3. Напишите функцию getstack, возвращающую текущий стек
вызовов в виде массива строк. Сама функция getstack
не должна фигурировать в стеке.
1. Самый простой способ — просто повторить операторы finally
в нескольких местах. Однако попробуйте построить такую конструкцию,
чтобы эти операторы можно было написать ровно один раз.
2. Для начала обратитесь к документации: