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

Что такое триггеры в базе данных и когда их применяют?

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

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

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

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

Триггеры в базе данных: назначение и практическое применение

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

Основной синтаксис (PostgreSQL)

-- Создание триггера для аудита
CREATE OR REPLACE FUNCTION log_user_changes()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO user_audit_log (user_id, action, old_data, new_data, changed_at)
    VALUES (
        NEW.id,
        TG_OP,
        row_to_json(OLD),
        row_to_json(NEW),
        NOW()
    );
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Привязка триггера к таблице
CREATE TRIGGER user_change_trigger
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION log_user_changes();

Типы триггеров

По времени выполнения:

-- BEFORE: выполняется ДО операции
CREATE TRIGGER validate_before_insert
BEFORE INSERT ON users
FOR EACH ROW
EXECUTE FUNCTION validate_user_data();

-- AFTER: выполняется ПОСЛЕ операции
CREATE TRIGGER log_after_insert
AFTER INSERT ON users
FOR EACH ROW
EXECUTE FUNCTION log_user_creation();

По типу операции:

-- На вставку
BEFORE INSERT ON users FOR EACH ROW

-- На обновление
BEFORE UPDATE ON users FOR EACH ROW

-- На удаление
BEFORE DELETE ON users FOR EACH ROW

-- На несколько операций
BEFORE INSERT OR UPDATE ON users FOR EACH ROW

Практический пример 1: Аудит изменений

-- Таблица для хранения истории
CREATE TABLE user_audit (
    id SERIAL PRIMARY KEY,
    user_id INT NOT NULL,
    action VARCHAR(10),
    old_data JSONB,
    new_data JSONB,
    changed_at TIMESTAMP DEFAULT NOW()
);

-- Функция триггера
CREATE OR REPLACE FUNCTION audit_user_changes()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'DELETE' THEN
        INSERT INTO user_audit (user_id, action, old_data, changed_at)
        VALUES (OLD.id, TG_OP, row_to_json(OLD), NOW());
    ELSIF TG_OP = 'UPDATE' THEN
        INSERT INTO user_audit (user_id, action, old_data, new_data, changed_at)
        VALUES (NEW.id, TG_OP, row_to_json(OLD), row_to_json(NEW), NOW());
    ELSIF TG_OP = 'INSERT' THEN
        INSERT INTO user_audit (user_id, action, new_data, changed_at)
        VALUES (NEW.id, TG_OP, row_to_json(NEW), NOW());
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER user_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION audit_user_changes();

-- Результат: каждое изменение логируется автоматически
-- UPDATE users SET name = 'John' WHERE id = 1;
-- В user_audit появится запись с информацией об изменении

Практический пример 2: Валидация данных

-- Триггер BEFORE INSERT для проверки
CREATE OR REPLACE FUNCTION validate_order()
RETURNS TRIGGER AS $$
BEGIN
    -- Проверяем что количество > 0
    IF NEW.quantity <= 0 THEN
        RAISE EXCEPTION 'Quantity must be > 0';
    END IF;
    
    -- Проверяем что товар существует
    IF NOT EXISTS (SELECT 1 FROM products WHERE id = NEW.product_id) THEN
        RAISE EXCEPTION 'Product not found';
    END IF;
    
    -- Проверяем что цена корректна
    IF NEW.total_price < 0 THEN
        RAISE EXCEPTION 'Total price cannot be negative';
    END IF;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER order_validation_trigger
BEFORE INSERT OR UPDATE ON orders
FOR EACH ROW
EXECUTE FUNCTION validate_order();

-- Попытка вставить невалидный заказ
-- INSERT INTO orders (product_id, quantity, total_price) VALUES (999, -5, 100);
-- ERROR: Quantity must be > 0

Практический пример 3: Автоматический update_at

-- Таблица с временем последнего обновления
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    price DECIMAL,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Триггер автоматически обновляет updated_at
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER product_update_timestamp
BEFORE UPDATE ON products
FOR EACH ROW
EXECUTE FUNCTION update_timestamp();

-- При UPDATE автоматически обновляется updated_at
-- UPDATE products SET price = 99 WHERE id = 1;
-- updated_at автоматически = текущее время

Практический пример 4: Обновление денормализованных данных

-- Таблица заказов
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT,
    total_amount DECIMAL,
    item_count INT DEFAULT 0
);

-- Таблица товаров в заказе
CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INT REFERENCES orders(id),
    product_id INT,
    quantity INT,
    price DECIMAL
);

-- Триггер: при добавлении товара в заказ обновляем total и count
CREATE OR REPLACE FUNCTION update_order_totals()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        UPDATE orders
        SET total_amount = total_amount + (NEW.quantity * NEW.price),
            item_count = item_count + NEW.quantity
        WHERE id = NEW.order_id;
    
    ELSIF TG_OP = 'DELETE' THEN
        UPDATE orders
        SET total_amount = total_amount - (OLD.quantity * OLD.price),
            item_count = item_count - OLD.quantity
        WHERE id = OLD.order_id;
    
    ELSIF TG_OP = 'UPDATE' THEN
        UPDATE orders
        SET total_amount = total_amount - (OLD.quantity * OLD.price) + (NEW.quantity * NEW.price),
            item_count = item_count - OLD.quantity + NEW.quantity
        WHERE id = NEW.order_id;
    END IF;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER order_items_trigger
AFTER INSERT OR UPDATE OR DELETE ON order_items
FOR EACH ROW
EXECUTE FUNCTION update_order_totals();

Когда применять триггеры

ХОРОШО использовать:

  1. Аудит — логирование всех изменений
  2. Денормализация — обновление calculated полей (count, total)
  3. Каскадные операции — при удалении одной таблицы обновить другую
  4. Валидация (BEFORE триггеры) — проверка data integrity
  5. Синхронизация — обновление related данных

ПЛОХО использовать (вместо триггеров):

  1. Бизнес-логика — это должно быть в коде
  2. Сложные вычисления — slow в БД
  3. Логирование в другие системы — используй message queue
  4. Интеграция с external сервисами — API calls в БД опасны

Пример из Java приложения

// ТАК (без триггера) - приложение в ответе за логику
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private AuditRepository auditRepository;
    
    @Transactional
    public void updateUser(Long id, UserUpdateRequest request) {
        User old = userRepository.findById(id).orElseThrow();
        User user = userRepository.save(new User(request));
        
        // Аудит вручную
        auditRepository.save(new Audit(
            user.getId(),
            "UPDATE",
            toJson(old),
            toJson(user)
        ));
    }
}

// ТАК (с триггером) - БД сама логирует
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public void updateUser(Long id, UserUpdateRequest request) {
        // Триггер в БД сам логирует
        userRepository.save(new User(request));
    }
}

Проблемы с триггерами

-- ПРОБЛЕМА 1: Hard to debug
-- Где логируется изменение? В коде? В триггере? Оба места?

-- ПРОБЛЕМА 2: Performance
-- Триггер выполняется для КАЖДОЙ строки
INSERT INTO users SELECT ... FROM source; -- Медленнее с триггером

-- ПРОБЛЕМА 3: Скрытая логика
-- Разработчик делает UPDATE и не знает что произойдёт триггер

-- ПРОБЛЕМА 4: Сложная отладка
-- Ошибка в триггере может crash всю операцию

-- ПРОБЛЕМА 5: Миграция
-- Когда менять БД или версию, нужно запомнить триггеры

Best Practices

-- 1. Триггеры только для data integrity
CREATE TRIGGER data_integrity_check
BEFORE INSERT OR UPDATE ON orders
FOR EACH ROW
EXECUTE FUNCTION validate_order_integrity();

-- 2. Простые и быстрые триггеры
CREATE TRIGGER fast_update
AFTER INSERT ON order_items
FOR EACH ROW
EXECUTE FUNCTION update_order_count(); -- Только COUNT, не сложные вычисления

-- 3. Документировать триггеры
COMMENT ON TRIGGER user_audit_trigger ON users
IS 'Logs all changes to user table for compliance';

-- 4. Версионировать миграции триггеров
-- migration_20240315_001_create_audit_trigger.sql

Итог

Триггеры — это мощный инструмент для:

  • Обеспечения data integrity
  • Аудита изменений
  • Автоматизации связанных операций

НО используй только когда:

  1. Логика ВСЕГДА должна выполняться (независимо от клиента)
  2. Это критично для БД integrity
  3. Простая и быстрая операция

Для сложной бизнес-логики используй код приложения, а не триггеры.