Triggers — Otomatik Tetiklenen İşlemler
Giriş — "Her Sipariş Oluşturulduğunda Stoku Otomatik Düş"
Bir e-ticaret sisteminde sipariş oluşturulduğunda birçok şeyin otomatik olması gerekir: stok düşmeli, müşterinin sipariş sayısı güncellenmeli, belki bir log kaydı tutulmalı. Bu işlemleri uygulama kodunda yapmak mümkün ama riskli — birisi doğrudan veritabanına bağlanıp INSERT yaparsa, uygulama kodundaki mantık çalışmaz.
Trigger, bir tablo üzerinde INSERT, UPDATE veya DELETE işlemi gerçekleştiğinde otomatik olarak çalışan bir SQL programıdır. Uygulama katmanından bağımsızdır — veri nereden gelirse gelsin, trigger tetiklenir.
🎯 Analoji: Trigger'ı bir evin alarm sistemi gibi düşün. Kapı açıldığında (INSERT), pencere kırıldığında (UPDATE), bir eşya çıkarıldığında (DELETE) alarm otomatik çalışır. Kim kapıyı açarsa açsın — ev sahibi, hırsız, tamirci — alarm her durumda devreye girer. Trigger da aynı: veri kim tarafından değiştirilirse değiştirilsin, otomatik çalışır.
Trigger Temelleri
Syntax
CREATE TRIGGER trigger_adı
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON tablo_adı
FOR EACH ROW
BEGIN
-- Tetiklenecek işlemler
END;Bir trigger tanımlarken 3 karar vermen gerekir:
Zamanlama:
BEFORE(işlemden önce) veyaAFTER(işlemden sonra)Olay:
INSERT,UPDATEveyaDELETETablo: Hangi tablo üzerinde?
NEW ve OLD — Eski ve Yeni Değerler
Trigger içinde iki özel kelime var:
NEW: INSERT veya UPDATE'deki yeni değerler
OLD: UPDATE veya DELETE'deki eski değerler
| İşlem | OLD | NEW |
|---|---|---|
| INSERT | Yok | ✅ Eklenen satır |
| UPDATE | ✅ Eski değerler | ✅ Yeni değerler |
| DELETE | ✅ Silinen satır | Yok |
AFTER INSERT Trigger — En Yaygın Kullanım
Sipariş Log'u
DELIMITER //
CREATE TRIGGER after_order_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT INTO order_log (order_id, action, action_date, details)
VALUES (
NEW.order_id,
'INSERT',
NOW(),
CONCAT('Yeni sipariş: müşteri=', NEW.customer_id,
', tutar=', NEW.total_amount)
);
END //
DELIMITER ;Artık orders tablosuna her INSERT yapıldığında, order_log tablosuna otomatik kayıt oluşturulur. Uygulama kodunu değiştirmene gerek yok.
Stok Güncelleme
DELIMITER //
CREATE TRIGGER after_order_item_insert
AFTER INSERT ON order_items
FOR EACH ROW
BEGIN
-- Stoku otomatik düş
UPDATE products
SET stock_quantity = stock_quantity - NEW.quantity
WHERE product_id = NEW.product_id;
-- Stok uyarısı log'u
INSERT INTO stock_alerts (product_id, alert_type, alert_date, details)
SELECT
NEW.product_id,
'LOW_STOCK',
NOW(),
CONCAT('Kalan stok: ', stock_quantity - NEW.quantity)
FROM products
WHERE product_id = NEW.product_id
AND stock_quantity - NEW.quantity < 10; -- Stok 10'un altına düşerse
END //
DELIMITER ;BEFORE INSERT Trigger — Veri Doğrulama ve Otomatik Değer Atama
BEFORE trigger'lar, veri tabloya yazılmadan önce çalışır. Doğrulama, varsayılan değer atama ve veri dönüştürme için idealdir.
DELIMITER //
CREATE TRIGGER before_customer_insert
BEFORE INSERT ON customers
FOR EACH ROW
BEGIN
-- E-posta küçük harfe çevir
SET NEW.email = LOWER(TRIM(NEW.email));
-- Telefon numarasını formatla
SET NEW.phone = REPLACE(REPLACE(REPLACE(NEW.phone, ' ', ''), '-', ''), '(', '');
SET NEW.phone = REPLACE(NEW.phone, ')', '');
-- Kayıt tarihini otomatik ata
IF NEW.registration_date IS NULL THEN
SET NEW.registration_date = NOW();
END IF;
-- Geçersiz e-posta kontrolü
IF NEW.email NOT LIKE '%@%.%' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Geçersiz e-posta adresi';
END IF;
END //
DELIMITER ;BEFORE INSERT'te NEW.sütun = değer ile veriyi değiştirebilirsin — veri tabloya bu değiştirilmiş haliyle yazılır. AFTER INSERT'te NEW salt okunurdur.
Fiyat Doğrulama
DELIMITER //
CREATE TRIGGER before_product_insert
BEFORE INSERT ON products
FOR EACH ROW
BEGIN
-- Negatif fiyat engelle
IF NEW.price < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Ürün fiyatı negatif olamaz';
END IF;
-- Maksimum fiyat kontrolü
IF NEW.price > 1000000 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Ürün fiyatı 1.000.000 TL üzerinde olamaz';
END IF;
-- Stok varsayılan değeri
IF NEW.stock_quantity IS NULL THEN
SET NEW.stock_quantity = 0;
END IF;
END //
DELIMITER ;UPDATE Trigger — Değişiklik İzleme (Audit Trail)
DELIMITER //
CREATE TRIGGER after_product_update
AFTER UPDATE ON products
FOR EACH ROW
BEGIN
-- Fiyat değişikliği log'u
IF OLD.price != NEW.price THEN
INSERT INTO price_history (
product_id, old_price, new_price, change_date, change_pct
) VALUES (
NEW.product_id,
OLD.price,
NEW.price,
NOW(),
ROUND((NEW.price - OLD.price) * 100.0 / OLD.price, 2)
);
END IF;
-- Stok değişikliği log'u
IF OLD.stock_quantity != NEW.stock_quantity THEN
INSERT INTO stock_history (
product_id, old_quantity, new_quantity, change_date
) VALUES (
NEW.product_id,
OLD.stock_quantity,
NEW.stock_quantity,
NOW()
);
END IF;
END //
DELIMITER ;Bu trigger sayesinde ürün fiyatı veya stoku her değiştiğinde otomatik log tutulur. "Bu ürünün fiyatı ne zaman, ne kadar değişti?" sorusuna anında cevap verebilirsin.
BEFORE UPDATE — Değişikliği Engelleme veya Modifiye Etme
DELIMITER //
CREATE TRIGGER before_order_update
BEFORE UPDATE ON orders
FOR EACH ROW
BEGIN
-- Tamamlanmış siparişin statusu değiştirilemez
IF OLD.status = 'completed' AND NEW.status != 'completed' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Tamamlanmış sipariş durumu değiştirilemez';
END IF;
-- İptal edilen siparişte tutar sıfırlanır
IF NEW.status = 'cancelled' AND OLD.status != 'cancelled' THEN
SET NEW.total_amount = 0;
END IF;
-- Son güncelleme tarihini otomatik ayarla
SET NEW.updated_at = NOW();
END //
DELIMITER ;DELETE Trigger — Silme İşlemi İzleme
DELIMITER //
CREATE TRIGGER before_customer_delete
BEFORE DELETE ON customers
FOR EACH ROW
BEGIN
-- Aktif siparişi olan müşteri silinemez
DECLARE v_active_orders INT;
SELECT COUNT(*) INTO v_active_orders
FROM orders
WHERE customer_id = OLD.customer_id
AND status IN ('pending', 'processing', 'shipped');
IF v_active_orders > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Aktif siparişi olan müşteri silinemez';
END IF;
-- Silinen müşteri bilgisini arşivle
INSERT INTO deleted_customers_archive (
customer_id, first_name, last_name, email, deleted_at
) VALUES (
OLD.customer_id, OLD.first_name, OLD.last_name, OLD.email, NOW()
);
END //
DELIMITER ;Sipariş Silme — Stok Geri Yükleme
DELIMITER //
CREATE TRIGGER after_order_item_delete
AFTER DELETE ON order_items
FOR EACH ROW
BEGIN
-- Silinen sipariş kaleminin stokunu geri yükle
UPDATE products
SET stock_quantity = stock_quantity + OLD.quantity
WHERE product_id = OLD.product_id;
END //
DELIMITER ;Trigger Zinciri ve Dikkat Noktaları
Trigger Sırası (MySQL 5.7.2+)
Aynı tablo ve olay için birden fazla trigger varsa, sıra belirtilebilir:
CREATE TRIGGER trigger_1
BEFORE INSERT ON orders
FOR EACH ROW
FOLLOWS trigger_0 -- trigger_0'dan sonra çalış
BEGIN
...
END;
CREATE TRIGGER trigger_2
BEFORE INSERT ON orders
FOR EACH ROW
PRECEDES trigger_1 -- trigger_1'den önce çalış
BEGIN
...
END;Trigger İçinde Trigger (Cascading)
-- Trigger 1: orders'a INSERT → order_log'a INSERT
-- Trigger 2: order_log'a INSERT → notification'a INSERT
-- Bu zincirleme (cascading) trigger — dikkatli ol!⚠️ Dikkat: Cascading trigger'lar debug etmesi zor hatalara neden olabilir. Trigger A, trigger B'yi tetikler, o da trigger C'yi tetikler... Hangi trigger ne yaptı bulmak kabus olur. Mümkünse trigger zincirlerini kısa tut.
Trigger Performans Etkisi
-- Her INSERT'te trigger çalışır — toplu INSERT'lerde dikkat!
-- ❌ 100.000 satırlık bulk INSERT — her satır için trigger çalışır
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
SELECT ... FROM temp_import; -- 100.000 kez trigger tetiklenir!
-- Çözümler:
-- 1. Trigger'ı geçici olarak devre dışı bırak
-- MySQL'de doğrudan DISABLE TRIGGER yok ama:
-- SET @TRIGGER_DISABLED = 1; -- Trigger içinde kontrol et
-- 2. Bulk işlem sonrası toplu güncelleme yapTrigger Yönetimi
-- Trigger'ları listele
SHOW TRIGGERS FROM ecommerce;
-- Belirli tablonun trigger'ları
SHOW TRIGGERS FROM ecommerce WHERE `Table` = 'orders';
-- Trigger tanımını göster
SHOW CREATE TRIGGER after_order_insert;
-- Trigger'ı sil
DROP TRIGGER IF EXISTS after_order_insert;
-- MySQL'de ALTER TRIGGER yok — sil ve yeniden oluşturGerçek Dünya Örneği — Tam Audit Sistemi
-- Genel audit log tablosu
CREATE TABLE audit_log (
log_id BIGINT AUTO_INCREMENT PRIMARY KEY,
table_name VARCHAR(100),
action ENUM('INSERT', 'UPDATE', 'DELETE'),
record_id INT,
old_values JSON,
new_values JSON,
changed_by VARCHAR(100),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Orders tablosu için tam audit trigger'ı
DELIMITER //
CREATE TRIGGER orders_audit_insert
AFTER INSERT ON orders FOR EACH ROW
BEGIN
INSERT INTO audit_log (table_name, action, record_id, new_values, changed_by)
VALUES ('orders', 'INSERT', NEW.order_id,
JSON_OBJECT(
'customer_id', NEW.customer_id,
'total_amount', NEW.total_amount,
'status', NEW.status
),
CURRENT_USER()
);
END //
CREATE TRIGGER orders_audit_update
AFTER UPDATE ON orders FOR EACH ROW
BEGIN
INSERT INTO audit_log (table_name, action, record_id, old_values, new_values, changed_by)
VALUES ('orders', 'UPDATE', NEW.order_id,
JSON_OBJECT(
'total_amount', OLD.total_amount,
'status', OLD.status
),
JSON_OBJECT(
'total_amount', NEW.total_amount,
'status', NEW.status
),
CURRENT_USER()
);
END //
CREATE TRIGGER orders_audit_delete
AFTER DELETE ON orders FOR EACH ROW
BEGIN
INSERT INTO audit_log (table_name, action, record_id, old_values, changed_by)
VALUES ('orders', 'DELETE', OLD.order_id,
JSON_OBJECT(
'customer_id', OLD.customer_id,
'total_amount', OLD.total_amount,
'status', OLD.status
),
CURRENT_USER()
);
END //
DELIMITER ;Bu audit sistemi, orders tablosundaki her değişikliği JSON formatında kaydeder — kim, ne zaman, ne değiştirdi, eski ve yeni değerler neydi.
Ne Zaman Trigger Kullanmalı, Ne Zaman Kullanmamalı?
✅ Kullan
Audit log / değişiklik takibi — Kim ne değiştirdi?
Veri doğrulama — BEFORE trigger ile geçersiz veriyi engelle
Otomatik değer atama — created_at, updated_at gibi
Denormalizasyon güncelleme — Özet tabloları otomatik güncelle
Referans bütünlüğü — FK constraint'in yetersiz kaldığı durumlar
❌ Kullanma
Karmaşık iş mantığı — Uygulama katmanında yap, test edilebilir olsun
Harici servis çağrıları — E-posta gönderme, API çağrısı trigger'dan yapılmaz
Performans kritik toplu işlemler — Trigger her satırda çalışır, toplu işlemleri yavaşlatır
Zincirleme trigger'lar — Debug etmesi imkansız hale gelir
PostgreSQL Farklılıkları
-- PostgreSQL: Trigger function ayrı tanımlanır
CREATE OR REPLACE FUNCTION orders_audit_func()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO audit_log (table_name, action, record_id, new_values)
VALUES ('orders', 'INSERT', NEW.order_id, row_to_json(NEW));
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO audit_log (table_name, action, record_id, old_values, new_values)
VALUES ('orders', 'UPDATE', NEW.order_id, row_to_json(OLD), row_to_json(NEW));
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO audit_log (table_name, action, record_id, old_values)
VALUES ('orders', 'DELETE', OLD.order_id, row_to_json(OLD));
END IF;
RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;
-- Tek trigger function, üç olay:
CREATE TRIGGER orders_audit
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW EXECUTE FUNCTION orders_audit_func();
-- MySQL'de tek trigger'da birden fazla olay desteklenmez
-- PostgreSQL: Statement-level trigger (tablo bazlı, satır bazlı değil)
CREATE TRIGGER after_bulk_insert
AFTER INSERT ON orders
FOR EACH STATEMENT EXECUTE FUNCTION notify_admin();
-- MySQL'de FOR EACH STATEMENT yok — sadece FOR EACH ROWÖzet
Trigger bir tablo üzerinde INSERT/UPDATE/DELETE olduğunda otomatik çalışan SQL programıdır
BEFORE trigger: veri yazılmadan önce — doğrulama, dönüştürme, engelleme
AFTER trigger: veri yazıldıktan sonra — log tutma, bağımlı tablo güncelleme
NEW: eklenen/güncellenen yeni değerler, OLD: güncellenen/silinen eski değerler
SIGNAL ile özel hata fırlatarak işlemi engelleyebilirsin
Audit trail (değişiklik takibi) için en güçlü araç
Trigger her satır için çalışır — toplu işlemlerde performansı etkiler
Sıkça Yapılan Hatalar
Trigger içinde aynı tabloyu güncellemek — MySQL'de trigger, tetikleyen tabloda DML yapamaz.
BEFOREtrigger'daSET NEW.sütun = değerkullan.Cascading trigger'lar oluşturmak — Trigger A → trigger B → trigger C zinciri debug kabusudur. Mümkünse doğrudan yaz.
Trigger'ı performans testinden geçirmemek — Trigger her satırda çalışır. 100.000 satırlık bulk INSERT'te 100.000 kez tetiklenir. Test et!
SIGNAL'ı BEFORE yerine AFTER'da kullanmak — AFTER'da SIGNAL fırlatmak işlemi geri alır ama veri zaten yazılmış olabilir. Doğrulama her zaman BEFORE'da yap.
Trigger'ların varlığını unutmak — Aylar sonra "bu log kaydı nereden geliyor?" diye sorgularken, trigger'ların varlığını hatırlayamayan geliştiriciler çok yaygın. Trigger'ları belgele.
AI Asistan
Sorularını yanıtlamaya hazır