← Назад к вопросам

Что такое триггеры в SQL и когда их использовать?

2.0 Middle🔥 201 комментариев
#SQL и базы данных

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Триггеры в SQL: Назначение и применение

Триггер (Trigger) — это специальная процедура в БД, которая автоматически выполняется при определённых событиях (INSERT, UPDATE, DELETE). Триггеры позволяют автоматизировать бизнес-логику на уровне БД.

Что такое триггер?

Триггер — это правило вида: «КОГДА событие X ПРОИЗОЙДЁТ, ВЫПОЛНИ действие Y».

Структура триггера:

CREATE TRIGGER trigger_name
BEFORE|AFTER INSERT|UPDATE|DELETE
ON table_name
FOR EACH ROW
BEGIN
    -- код триггера
END;

Временные точки:

  • BEFORE — выполняется ДО события
  • AFTER — выполняется ПОСЛЕ события

События:

  • INSERT — при добавлении строк
  • UPDATE — при изменении строк
  • DELETE — при удалении строк

Пример 1: Автоматическое обновление timestamp

CREATE TRIGGER update_user_timestamp
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
    SET NEW.updated_at = NOW();
END;

-- Теперь при любом UPDATE автоматически обновляется updated_at
UPDATE users SET email = 'new@example.com' WHERE user_id = 1;
-- updated_at автоматически установится на текущее время

Пример 2: Проверка бизнес-правил

CREATE TRIGGER check_order_amount
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
    IF NEW.amount <= 0 THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Order amount must be positive';
    END IF;
    
    IF NEW.amount > 1000000 THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Order amount too large';
    END IF;
END;

-- Теперь невозможно вставить заказ с недействительной суммой
INSERT INTO orders (customer_id, amount) VALUES (1, -100);  -- Ошибка!
INSERT INTO orders (customer_id, amount) VALUES (1, 50000);  -- OK

Пример 3: Ведение аудит-логов

CREATE TABLE user_audit_log (
    log_id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT,
    action VARCHAR(50),  -- 'INSERT', 'UPDATE', 'DELETE'
    old_email VARCHAR(255),
    new_email VARCHAR(255),
    changed_at TIMESTAMP
);

CREATE TRIGGER audit_user_changes
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
    INSERT INTO user_audit_log (user_id, action, old_email, new_email, changed_at)
    VALUES (NEW.user_id, 'UPDATE', OLD.email, NEW.email, NOW());
END;

-- Каждое изменение email автоматически логируется
UPDATE users SET email = 'newemail@example.com' WHERE user_id = 1;

-- Проверим логи
SELECT * FROM user_audit_log WHERE user_id = 1;

Пример 4: Управление балансом счёта

CREATE TABLE accounts (
    account_id INT PRIMARY KEY,
    balance DECIMAL(10, 2),
    updated_at TIMESTAMP
);

CREATE TABLE transactions (
    transaction_id INT PRIMARY KEY AUTO_INCREMENT,
    account_id INT,
    amount DECIMAL(10, 2),
    type VARCHAR(10),  -- 'debit', 'credit'
    created_at TIMESTAMP,
    FOREIGN KEY (account_id) REFERENCES accounts(account_id)
);

-- Триггер: автоматически обновляет баланс при новой транзакции
CREATE TRIGGER update_account_balance
AFTER INSERT ON transactions
FOR EACH ROW
BEGIN
    IF NEW.type = 'credit' THEN
        UPDATE accounts SET balance = balance + NEW.amount WHERE account_id = NEW.account_id;
    ELSE
        UPDATE accounts SET balance = balance - NEW.amount WHERE account_id = NEW.account_id;
    END IF;
END;

-- Транзакция автоматически обновит баланс
INSERT INTO transactions (account_id, amount, type) VALUES (1, 100, 'credit');
SELECT * FROM accounts WHERE account_id = 1;  -- balance увеличился на 100

Пример 5: Каскадное удаление с логированием

CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

CREATE TABLE order_items (
    item_id INT PRIMARY KEY,
    order_id INT,
    FOREIGN KEY (order_id) REFERENCES orders(order_id)
);

CREATE TABLE deleted_items_log (
    log_id INT PRIMARY KEY AUTO_INCREMENT,
    item_id INT,
    order_id INT,
    deleted_at TIMESTAMP
);

-- При удалении заказа логируем все удаления
CREATE TRIGGER log_deleted_items
BEFORE DELETE ON order_items
FOR EACH ROW
BEGIN
    INSERT INTO deleted_items_log (item_id, order_id, deleted_at)
    VALUES (OLD.item_id, OLD.order_id, NOW());
END;

DELETE FROM order_items WHERE order_id = 123;  -- Все удаления залогированы

Когда использовать триггеры

Используй триггеры для:

Поддержание целостности данных

-- Проверка что email уникален
CREATE TRIGGER check_unique_email
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
    IF EXISTS(SELECT 1 FROM users WHERE email = NEW.email) THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Email already exists';
    END IF;
END;

Автоматизация часто повторяющейся логики

-- Каждое изменение — обновляем timestamp
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON users
FOR EACH ROW
SET NEW.updated_at = NOW();

Аудит и логирование

-- Все изменения логируются автоматически
CREATE TRIGGER audit_insert
AFTER INSERT ON orders
FOR EACH ROW
INSERT INTO audit_log VALUES (NOW(), 'INSERT', NEW.order_id);

Когда НЕ использовать триггеры

❌ НЕ используй триггеры для:

Сложной бизнес-логики

-- ПЛОХО: логика в триггере
CREATE TRIGGER complex_business_logic
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
    -- 100 строк кода...
    -- Проверки, расчёты, обновления...
    -- НЕВОЗМОЖНО ТЕСТИРОВАТЬ!
END;

-- ХОРОШО: логика в приложении
def create_order(customer_id, items):
    # Business logic здесь
    # Легко тестировать
    save_to_db(order)

Обновлений в множественных таблицах

-- ПЛОХО: триггер что-то меняет, это меняет другой триггер...
-- Результат: каскад изменений, сложно отследить что произошло

-- ХОРОШО: делай это в приложении явно

Высоконагруженных систем Триггеры замедляют INSERT/UPDATE. На миллионах записей это заметно.

Плюсы и минусы триггеров

ПлюсМинус
Автоматизация повторяющейся логикиСкрытая сложность (сложно отследить)
Целостность данных на уровне БДСложнее тестировать
Работает для ЛЮБОГО клиента (web, API, batch)Замедляет INSERT/UPDATE
Синхронизация между таблицамиСложно с миграциями
Отладка триггеров — кошмар

Когда триггеры спасают

Сценарий: Фото в социальной сети

-- Есть таблица фото
CREATE TABLE photos (
    photo_id INT PRIMARY KEY,
    user_id INT,
    created_at TIMESTAMP,
    likes_count INT DEFAULT 0
);

-- Триггер: каждый новый лайк обновляет счётчик
CREATE TABLE photo_likes (
    like_id INT PRIMARY KEY AUTO_INCREMENT,
    photo_id INT,
    user_id INT,
    FOREIGN KEY (photo_id) REFERENCES photos(photo_id)
);

CREATE TRIGGER increment_likes
AFTER INSERT ON photo_likes
FOR EACH ROW
BEGIN
    UPDATE photos SET likes_count = likes_count + 1 WHERE photo_id = NEW.photo_id;
END;

-- Результат: счётчик ВСЕГДА синхронизирован
-- Работает для ЛЮБОГО источника данных

Без триггера пришлось бы каждый раз считать:

SELECT COUNT(*) FROM photo_likes WHERE photo_id = 123;  -- МЕДЛЕННО

Best Practices

-- 1. Держи триггеры простыми
CREATE TRIGGER simple_trigger
AFTER INSERT ON users
FOR EACH ROW
BEGIN
    UPDATE stats SET user_count = user_count + 1;
END;

-- 2. Документируй зачем триггер
-- 3. Избегай множественных триггеров на одну таблицу
-- 4. Не пиши сложную логику — это дело приложения
-- 5. Помни о производительности

-- 6. Удаляй неиспользуемые триггеры
DROP TRIGGER IF EXISTS old_trigger;

-- 7. Смотри список триггеров
SHOW TRIGGERS;  -- MySQL
SELECT * FROM information_schema.triggers;  -- PostgreSQL

Альтернативы триггерам

В современных приложениях:

  1. Application-level logic (в коде приложения)

    • Легче тестировать
    • Более понятно
    • Контролируешь параллелизм
  2. Event-driven architecture (Kafka, Pub/Sub)

    • Когда что-то меняется → отправляем событие
    • Другие сервисы слушают события
  3. Change Data Capture (CDC) (Debezium, AWS DMS)

    • Автоматически отслеживаешь изменения
    • Реплицируешь в другие системы

Вывод

Триггеры — мощный инструмент, но нужно использовать осторожно. Они хороши для:

  • Автоматизации простой логики
  • Аудита и логирования
  • Синхронизации данных между таблицами

Но для сложной бизнес-логики лучше использовать приложение. Помни: то, что скрыто в триггере, сложнее отладить и тестировать.