← Назад к вопросам
Что такое триггеры в базе данных и когда их применяют?
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();
Когда применять триггеры
ХОРОШО использовать:
- Аудит — логирование всех изменений
- Денормализация — обновление calculated полей (count, total)
- Каскадные операции — при удалении одной таблицы обновить другую
- Валидация (BEFORE триггеры) — проверка data integrity
- Синхронизация — обновление related данных
ПЛОХО использовать (вместо триггеров):
- Бизнес-логика — это должно быть в коде
- Сложные вычисления — slow в БД
- Логирование в другие системы — используй message queue
- Интеграция с 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
- Аудита изменений
- Автоматизации связанных операций
НО используй только когда:
- Логика ВСЕГДА должна выполняться (независимо от клиента)
- Это критично для БД integrity
- Простая и быстрая операция
Для сложной бизнес-логики используй код приложения, а не триггеры.