MariaDB ve MySQL’de TRIGGER Kullanımı ve Otomatik Tetikleme
Veritabanı yönetiminde en güçlü ama bir o kadar da dikkatli kullanılması gereken araçlardan biri trigger’lardır. Bir tabloda veri değiştiğinde otomatik olarak başka işlemler gerçekleştirmek, loglama yapmak, tutarlılığı korumak veya bildirim göndermek istediğinizde trigger’lar imdadınıza yetişir. Bu yazıda MariaDB ve MySQL üzerinde trigger kullanımını gerçek dünya senaryolarıyla ele alacağız.
Trigger Nedir ve Ne İşe Yarar?
Trigger, bir tabloda INSERT, UPDATE veya DELETE işlemi gerçekleştiğinde otomatik olarak çalışan bir veritabanı nesnesidir. Uygulama katmanında yazmak zorunda kaldığınız bazı iş kurallarını doğrudan veritabanı seviyesinde tanımlayabilirsiniz. Bu sayede hangi uygulama veya araç üzerinden veri girişi yapılırsa yapılsın, kurallar her zaman geçerli olur.
Trigger kullanmanın en büyük avantajı tutarlılıktır. Birden fazla uygulama aynı veritabanına yazıyorsa, her birinde aynı iş mantığını tekrarlamak yerine trigger ile tek noktadan yönetebilirsiniz. Dezavantajı ise debug sürecini zorlaştırması ve yoğun yazma trafiğinde performans etkisi yaratabilmesidir. Bu dengeyi iyi kurmak gerekir.
Trigger Yapısı ve Temel Sözdizimi
MariaDB ve MySQL’de trigger oluşturmak için genel yapı şöyledir:
CREATE TRIGGER trigger_adi
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON tablo_adi
FOR EACH ROW
BEGIN
-- Yapılacak işlemler
END;
Burada birkaç önemli kavram var:
- BEFORE: İşlem gerçekleşmeden önce trigger çalışır. Veriyi değiştirme veya iptal etme şansınız olur.
- AFTER: İşlem gerçekleştikten sonra trigger çalışır. Loglama için ideal.
- FOR EACH ROW: Etkilenen her satır için trigger ayrı ayrı çalışır.
- NEW: INSERT ve UPDATE işlemlerinde yeni değerlere erişmek için kullanılır.
- OLD: UPDATE ve DELETE işlemlerinde eski değerlere erişmek için kullanılır.
Trigger yazarken delimiter değiştirmeniz gerekir, çünkü trigger gövdesi noktalı virgül içerir ve MySQL istemcisi bunu sorgu sonu olarak yorumlar.
DELIMITER $$
CREATE TRIGGER ornek_trigger
AFTER INSERT ON siparisler
FOR EACH ROW
BEGIN
INSERT INTO siparis_log (siparis_id, islem, tarih)
VALUES (NEW.id, 'Yeni sipariş eklendi', NOW());
END$$
DELIMITER ;
Gerçek Dünya Senaryosu 1: Stok Takip Sistemi
E-ticaret projelerinde en sık karşılaşılan ihtiyaçlardan biri stok yönetimidir. Sipariş verildiğinde ürün stoku otomatik azalmalı, sipariş iptal edildiğinde geri artmalıdır. Bunu uygulama kodunda yapmak yerine trigger ile garantiye alabiliriz.
Önce tablolarımızı oluşturalım:
CREATE TABLE urunler (
id INT PRIMARY KEY AUTO_INCREMENT,
ad VARCHAR(255) NOT NULL,
stok INT DEFAULT 0,
fiyat DECIMAL(10,2)
);
CREATE TABLE siparis_kalemleri (
id INT PRIMARY KEY AUTO_INCREMENT,
siparis_id INT,
urun_id INT,
miktar INT,
durum VARCHAR(50) DEFAULT 'aktif'
);
Şimdi sipariş eklendiğinde stok düşüren trigger:
DELIMITER $$
CREATE TRIGGER stok_azalt
AFTER INSERT ON siparis_kalemleri
FOR EACH ROW
BEGIN
-- Stok kontrolü
IF (SELECT stok FROM urunler WHERE id = NEW.urun_id) < NEW.miktar THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Yetersiz stok miktari';
END IF;
UPDATE urunler
SET stok = stok - NEW.miktar
WHERE id = NEW.urun_id;
END$$
DELIMITER ;
Sipariş iptal edildiğinde stok geri veren trigger:
DELIMITER $$
CREATE TRIGGER stok_geri_yukle
AFTER UPDATE ON siparis_kalemleri
FOR EACH ROW
BEGIN
IF OLD.durum != 'iptal' AND NEW.durum = 'iptal' THEN
UPDATE urunler
SET stok = stok + OLD.miktar
WHERE id = OLD.urun_id;
END IF;
END$$
DELIMITER ;
Bu yapı sayesinde bir DBA veya geliştirici doğrudan SQL ile sipariş girişi yaptığında bile stok tutarsız kalmaz.
Gerçek Dünya Senaryosu 2: Audit Log (Denetim Kaydı)
Finans ve sağlık sektöründe çalışıyorsanız, kimin ne zaman hangi veriyi değiştirdiğini kayıt altına almanız yasal zorunluluk olabilir. BEFORE ve AFTER trigger’larını birlikte kullanarak kapsamlı bir audit sistemi kurabilirsiniz.
CREATE TABLE kullaniciler (
id INT PRIMARY KEY AUTO_INCREMENT,
ad VARCHAR(100),
email VARCHAR(255),
maas DECIMAL(10,2),
guncelleme_tarihi TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE kullanici_audit (
id INT PRIMARY KEY AUTO_INCREMENT,
kullanici_id INT,
islem_turu VARCHAR(20),
eski_deger JSON,
yeni_deger JSON,
islem_tarihi TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
islem_yapan VARCHAR(100)
);
Şimdi UPDATE işlemleri için kapsamlı bir audit trigger’ı yazalım:
DELIMITER $$
CREATE TRIGGER kullanici_update_audit
AFTER UPDATE ON kullaniciler
FOR EACH ROW
BEGIN
INSERT INTO kullanici_audit (
kullanici_id,
islem_turu,
eski_deger,
yeni_deger,
islem_yapan
)
VALUES (
OLD.id,
'UPDATE',
JSON_OBJECT(
'ad', OLD.ad,
'email', OLD.email,
'maas', OLD.maas
),
JSON_OBJECT(
'ad', NEW.ad,
'email', NEW.email,
'maas', NEW.maas
),
USER()
);
END$$
DELIMITER ;
DELETE işlemleri için de benzer bir trigger:
DELIMITER $$
CREATE TRIGGER kullanici_delete_audit
BEFORE DELETE ON kullaniciler
FOR EACH ROW
BEGIN
INSERT INTO kullanici_audit (
kullanici_id,
islem_turu,
eski_deger,
yeni_deger,
islem_yapan
)
VALUES (
OLD.id,
'DELETE',
JSON_OBJECT(
'ad', OLD.ad,
'email', OLD.email,
'maas', OLD.maas
),
NULL,
USER()
);
END$$
DELIMITER ;
BEFORE DELETE kullandığımıza dikkat edin. Silme işlemi tamamlanmadan önce log kaydını oluşturuyoruz, böylece herhangi bir sorun durumunda kayıt kesinlikle düşer.
Gerçek Dünya Senaryosu 3: Otomatik Zaman Damgası ve Hesaplama
Bazı alanlarda manuel olarak güncelleme yapmak yerine trigger ile otomatik değer atamak pratik bir çözümdür. Sipariş toplam tutarını her kalem eklendiğinde otomatik hesaplayan bir örnek:
CREATE TABLE siparisler (
id INT PRIMARY KEY AUTO_INCREMENT,
musteri_id INT,
toplam_tutar DECIMAL(10,2) DEFAULT 0,
olusturma_tarihi TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
guncelleme_tarihi TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
DELIMITER $$
CREATE TRIGGER siparis_toplam_guncelle
AFTER INSERT ON siparis_kalemleri
FOR EACH ROW
BEGIN
UPDATE siparisler
SET toplam_tutar = (
SELECT SUM(sk.miktar * u.fiyat)
FROM siparis_kalemleri sk
JOIN urunler u ON sk.urun_id = u.id
WHERE sk.siparis_id = NEW.siparis_id
AND sk.durum != 'iptal'
)
WHERE id = NEW.siparis_id;
END$$
DELIMITER ;
Trigger Yönetimi: Listeleme, İnceleme ve Silme
Sistemde hangi trigger’ların olduğunu görmek için:
-- Tüm trigger'ları listele
SHOW TRIGGERS;
-- Belirli veritabanındaki trigger'lar
SHOW TRIGGERS FROM veritabani_adi;
-- Belirli tablodaki trigger'lar
SHOW TRIGGERS FROM veritabani_adi LIKE 'tablo_adi';
-- Trigger detayını görüntüle
SELECT * FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA = 'veritabani_adi'
AND EVENT_OBJECT_TABLE = 'tablo_adi'G
-- Trigger silme
DROP TRIGGER IF EXISTS trigger_adi;
DROP TRIGGER IF EXISTS veritabani_adi.trigger_adi;
Trigger’ı geçici olarak devre dışı bırakmak isterseniz, MariaDB ve MySQL’de bunu doğrudan yapacak bir komut yoktur. Bunun yerine trigger’ı drop edip işiniz bittikten sonra yeniden oluşturabilirsiniz. Bu yüzden trigger definitionlarını her zaman bir script dosyasında saklamanızı tavsiye ederim.
Trigger’larda Hata Yönetimi
SIGNAL komutu ile özel hata mesajları ve hata kodları üretebilirsiniz. Bu, uygulama tarafında anlamlı hata mesajları göstermek için çok önemlidir:
DELIMITER $$
CREATE TRIGGER maas_kontrol
BEFORE UPDATE ON kullaniciler
FOR EACH ROW
BEGIN
-- Maaş negatif olamaz
IF NEW.maas < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Maas negatif olamaz';
END IF;
-- Maaş bir anda %50'den fazla artamaz
IF NEW.maas > OLD.maas * 1.5 THEN
SIGNAL SQLSTATE '45001'
SET MESSAGE_TEXT = 'Maas artisi %50 sinirini asiyor, yonetici onayi gerekli';
END IF;
-- Email formatı kontrolü
IF NEW.email NOT LIKE '%@%.%' THEN
SIGNAL SQLSTATE '45002'
SET MESSAGE_TEXT = 'Gecersiz email formati';
END IF;
END$$
DELIMITER ;
Performans Konuları ve Dikkat Edilmesi Gerekenler
Trigger kullanırken göz önünde bulundurmanız gereken bazı kritik noktalar var:
- Döngüsel tetikleme riski: Bir trigger başka bir tabloya yazıyor, o tablodaki trigger da geri geliyor olabilir. MariaDB ve MySQL aynı tablo üzerinde döngüsel trigger’ı engellemez ama farklı tablolar arasında döngü oluşabilir.
- Transaction davranışı: Trigger, tetikleyen işlemle aynı transaction içinde çalışır. Trigger başarısız olursa ana işlem de geri alınır.
- Yoğun yazma trafiği: Her INSERT için çalışan ağır bir trigger, milyonlarca kayıt işlenirken ciddi gecikmelere yol açabilir. Yüksek trafikli tablolarda trigger mantığını basit tutun.
- Debugging zorluğu: Trigger hataları bazen uygulamada anlaşılmaz hatalar olarak görünür.
SHOW WARNINGS;ve error log takibi hayat kurtarır. - Cascading etkiler: Bir trigger başka tablolar üzerinde UPDATE veya INSERT yaparsa, o tablolardaki trigger’lar da tetiklenebilir. Bu zincirleme etkiyi önceden düşünün.
Trigger’ların performans etkisini ölçmek için:
-- Slow query log'u aktif et
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
-- Trigger'lı işlemleri test et
INSERT INTO siparis_kalemleri (siparis_id, urun_id, miktar) VALUES (1, 5, 3);
-- Sonuçları incele
SHOW STATUS LIKE 'Slow_queries';
Pratik Senaryo: Kullanıcı Giriş Takibi
Güvenlik açısından son kullanıcı giriş hareketlerini veritabanında takip etmek isteyebilirsiniz:
CREATE TABLE kullanici_giris_log (
id INT PRIMARY KEY AUTO_INCREMENT,
kullanici_id INT,
giris_zamani TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_adresi VARCHAR(45),
sonuc VARCHAR(20)
);
CREATE TABLE kullaniciler_extended (
id INT PRIMARY KEY AUTO_INCREMENT,
kullanici_adi VARCHAR(100),
son_giris TIMESTAMP NULL,
basarisiz_giris_sayisi INT DEFAULT 0,
hesap_kilitli TINYINT DEFAULT 0
);
DELIMITER $$
CREATE TRIGGER giris_takip
AFTER INSERT ON kullanici_giris_log
FOR EACH ROW
BEGIN
IF NEW.sonuc = 'basarili' THEN
UPDATE kullaniciler_extended
SET son_giris = NEW.giris_zamani,
basarisiz_giris_sayisi = 0
WHERE id = NEW.kullanici_id;
ELSEIF NEW.sonuc = 'basarisiz' THEN
UPDATE kullaniciler_extended
SET basarisiz_giris_sayisi = basarisiz_giris_sayisi + 1
WHERE id = NEW.kullanici_id;
-- 5 başarısız girişte hesabı kilitle
UPDATE kullaniciler_extended
SET hesap_kilitli = 1
WHERE id = NEW.kullanici_id
AND basarisiz_giris_sayisi >= 5;
END IF;
END$$
DELIMITER ;
Bu trigger ile uygulama katmanında bu mantığı yazmak zorunda kalmıyorsunuz ve hangi kanaldan giriş yapılırsa yapılsın hesap kilitleme devreye giriyor.
Trigger’ları Backup ve Migration Süreçlerinde Yönetmek
mysqldump ile trigger’ları yedeklerken dikkatli olun:
# Trigger'lar dahil tam yedek
mysqldump --triggers --routines -u root -p veritabani_adi > yedek.sql
# Sadece trigger'ları dışarıda bırakarak yedek
mysqldump --skip-triggers -u root -p veritabani_adi > yedek_no_trigger.sql
# Büyük veri yüklemelerinde trigger'ları geçici devre dışı bırakmak için
# mysqldump çıktısındaki trigger'ları kaldırın, yükleme yapın, sonra tekrar ekleyin
Migration sırasında büyük veri yüklemeleri yapıyorsanız trigger’lar yükü önemli ölçüde yavaşlatabilir. Bu durumda:
-- Trigger'ı drop et
DROP TRIGGER IF EXISTS stok_azalt;
-- Toplu veri yükle
LOAD DATA INFILE '/tmp/siparis_kalemleri.csv'
INTO TABLE siparis_kalemleri
FIELDS TERMINATED BY ','
LINES TERMINATED BY 'n';
-- Stokları manuel güncelle
UPDATE urunler u
SET stok = stok - (
SELECT COALESCE(SUM(sk.miktar), 0)
FROM siparis_kalemleri sk
WHERE sk.urun_id = u.id
);
-- Trigger'ı yeniden oluştur
-- (Script dosyanızdan tekrar çalıştırın)
Sonuç
Trigger’lar doğru kullanıldığında veritabanı yönetiminin güçlü bir silahıdır. Stok yönetimi, audit loglama, veri doğrulama ve otomatik hesaplama gibi senaryolarda uygulama katmanına olan bağımlılığı azaltır ve veri tutarlılığını garanti altına alır.
Ancak her güçlü araç gibi trigger’lar da sorumlu kullanım ister. Karmaşık iş mantığını tamamını trigger’a yığmak, debug sürecini cehenneme çevirebilir ve performans sorunlarına davetiye çıkarabilir. Basit tutun, iyi dokümante edin ve trigger’larınızı mutlaka versiyon kontrolüne alın.
Sysadmin olarak şunu da ekleyeyim: Bir sistemi devraldığınızda ilk yapacağınız işlerden biri SHOW TRIGGERS; komutunu çalıştırmak olsun. Defalarca karşılaştım, kimsenin haberdar olmadığı trigger’lar sessizce çalışıyor ve beklenmedik davranışlara yol açıyor. Görünmez iş mantığı en tehlikeli olanıdır.
