Іноді тригери поводяться непередбачувано, і це може бути пов’язано з:
- Логічною помилкою в логіці функції, прив’язаній до тригера.
- Порушенням обмежень бази даних (наприклад, порушення унікальності або невідповідність типів даних).
- Проблемами у транзакціях, коли тригер викликає відкат змін через помилку.
- Рекурсією, якщо тригер викликає сам себе (часто випадково).
Щоб уникнути таких проблем, PostgreSQL дає можливість обробляти помилки всередині тригерів і їх функцій. Ці інструменти включають блоки EXCEPTION і оператор RAISE, які ми сьогодні розглянемо на прикладах.
Обробка помилок за допомогою блоку EXCEPTION
Блок EXCEPTION дозволяє перехоплювати помилки і виконувати певний код для їх обробки. Це схоже на використання try-catch у мовах програмування, таких як Python чи Java.
Блок EXCEPTION використовується у функціях PL/pgSQL ось так:
BEGIN
-- Основний код функції
EXCEPTION
WHEN <тип_помилки> THEN
-- Код обробки помилки
END;
Де <тип_помилки> — це конкретна помилка або група помилок, яку ти хочеш обробити (наприклад, unique_violation, division_by_zero і т.д.).
Приклад: логування помилок у тригерах
Уяви, що у нас є таблиця logs, куди ми хочемо записувати помилки, що виникають при вставці даних у таблицю students. Ось приклад:
Створюємо таблицю для логів
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
error_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
error_message TEXT
);
Створюємо функцію з обробкою помилок
CREATE OR REPLACE FUNCTION track_insert_errors()
RETURNS TRIGGER AS $$
BEGIN
-- Пробуємо виконати основний код
BEGIN
-- Приклад "помилкової" дії: ділення на 0
PERFORM 1 / (NEW.some_value - NEW.some_value);
EXCEPTION
WHEN division_by_zero THEN
-- Якщо сталася помилка ділення на 0, записуємо її в логи
INSERT INTO logs (error_message) VALUES ('Помилка ділення на 0 при вставці в students');
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Створюємо тригер
CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION track_insert_errors();
Тепер, якщо при вставці даних у таблицю students станеться помилка ділення на нуль, вона буде оброблена, а інформація про неї запишеться у таблицю logs.
Використання RAISE для діагностики та відладки
Оператор RAISE дозволяє виводити повідомлення про попередження, помилки чи інформацію для відладки. Це дуже корисний інструмент, коли ти намагаєшся зрозуміти, як працює (або не працює!) твій тригер.
Типи повідомлень RAISE:
DEBUG— повідомлення для відладки.NOTICE— звичайне інформаційне повідомлення.WARNING— попередження.EXCEPTION— повідомлення про помилку, яке завершує виконання функції.
Синтаксис RAISE:
RAISE <тип_повідомлення> 'Повідомлення';
Ти також можеш передавати значення змінних:
RAISE NOTICE 'Значення NEW.id = %', NEW.id;
Приклад: відладка значень у тригері
Припустимо, у нас виникає помилка при оновленні таблиці students, і ми хочемо дізнатися, які значення NEW і OLD викликають проблему. Для цього використовуємо RAISE:
CREATE OR REPLACE FUNCTION debug_student_update()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'OLD.id = %, NEW.id = %', OLD.id, NEW.id;
-- Приклад умови, що викликає помилку:
IF NEW.some_field IS NULL THEN
RAISE EXCEPTION 'Поле some_field не може бути NULL';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_update_students
AFTER UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION debug_student_update();
Тепер при кожному оновленні запису ти зможеш побачити значення OLD і NEW, а також отримати зрозуміле повідомлення про помилку, якщо вона виникне.
Транзакції у тригерах
Тригери виконуються у контексті транзакції. Це означає, що якщо десь всередині тригера чи його функції виникає помилка, вся транзакція буде відкотена. Це круто захищає базу даних від часткових змін.
Але таке поводження іноді створює труднощі:
- Якщо помилка всередині тригера пов’язана з некоректними даними, то іноді хочеться відкочувати тільки частину дій.
- Треба розуміти, що відкат транзакції включає не тільки тригер, а й всю операцію, яка його викликала.
Приклад: використання транзакцій у тригері
Для ілюстрації, уяви, що ми хочемо виконувати певну бізнес-логіку, яка включає дві операції: оновлення таблиці students і запис лога у logs. Якщо одна з цих операцій не вдалася, то вся транзакція відкочується.
CREATE OR REPLACE FUNCTION transactional_student_update()
RETURNS TRIGGER AS $$
BEGIN
-- Логування спроби оновлення
INSERT INTO logs (error_message) VALUES ('Спроба оновлення студента з id ' || NEW.id);
-- Перевіряємо бізнес-умови
IF NEW.some_value IS NULL THEN
RAISE EXCEPTION 'Поле some_value не може бути NULL';
END IF;
-- Якщо все ок, повертаємо NEW
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_update_students
BEFORE UPDATE ON students
FOR EACH ROW
EXECUTE FUNCTION transactional_student_update();
Типові помилки при роботі з тригерами та їх уникнення
Часто зустрічаються такі помилки розробників:
Рекурсивні тригери. Це трапляється, коли тригер вносить зміни, які знову запускають його виконання. Приклад вирішення: використовувати умову WHEN або додати прапорець для уникнення повторних викликів.
Відкат всієї транзакції через помилки. Часто це небажано, якщо тригер не пов’язаний напряму з основними даними. Приклад вирішення: грамотно використовувати блоки EXCEPTION.
Зайва відладочна інформація. Захаращує логи і ускладнює аналіз. Приклад вирішення: використовувати RAISE тільки під час розробки і тестування.
Зниження продуктивності. Складні тригери можуть сповільнювати операції INSERT, UPDATE чи DELETE. Приклад вирішення: мінімізувати логіку тригера і уникати запитів, що дають велике навантаження.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ