REPLACE ve INSERT ON DUPLICATE KEY UPDATE
Giriş — "Varsa Güncelle, Yoksa Ekle" İhtiyacı
Şöyle bir senaryo düşün: bir e-ticaret sitesinde ürün fiyatları her gün tedarikçiden güncelleniyor. Tedarikçi sana bir liste gönderiyor — bazı ürünler veritabanında zaten var (fiyat güncellemesi), bazıları yeni (eklenmesi gerekiyor). Her ürün için önce SELECT ile var mı yok mu kontrol edip, varsa UPDATE, yoksa INSERT mı yazacaksın?
Bu yaklaşım hem yavaş hem de race condition'a (yarış koşulu) açık. İşte UPSERT (Update + Insert) kavramı tam olarak bu sorunu çözer: "Eğer kayıt varsa güncelle, yoksa ekle."
🎯 Analoji: Kütüphaneye kitap getirdiğini düşün. Kütüphaneci rafı kontrol ediyor: kitap zaten varsa yerini günceller (belki başka rafa taşır), yoksa yeni kayıt oluşturur. Tek operasyon, iki olasılık.
REPLACE INTO — Sil ve Yeniden Ekle
REPLACE MySQL'e özgü bir komuttur. Mantığı basit ama biraz sert: varsa sil, sonra ekle. Yani güncelleme yapmaz, eski kaydı çöpe atıp yenisini oluşturur.
Sözdizimi
REPLACE INTO tablo_adi (sutun1, sutun2, ...)
VALUES (deger1, deger2, ...);Nasıl Çalışır?
INSERT yapmaya çalışır
Eğer PRIMARY KEY veya UNIQUE KEY çakışması yoksa → normal INSERT gibi ekler
Eğer çakışma varsa → eski satırı siler, yeni satırı ekler
-- Müşteri tablomuza bakalım
SELECT customer_id, first_name, email, city FROM customers WHERE customer_id = 1;
-- customer_id: 1, first_name: Ahmet, email: ahmet@email.com, city: İstanbul
-- REPLACE ile güncelleme
REPLACE INTO customers (customer_id, first_name, last_name, email, city)
VALUES (1, 'Ahmet', 'Yılmaz', 'ahmet@email.com', 'Ankara');
-- Query OK, 2 rows affected (1 silme + 1 ekleme = 2)
-- Kayıt yok — normal ekleme
REPLACE INTO customers (customer_id, first_name, last_name, email, city)
VALUES (99, 'Test', 'Kullanıcı', 'test@email.com', 'Bursa');
-- Query OK, 1 row affected (sadece ekleme)REPLACE'in Tehlikeleri
-- ⚠️ TEHLİKE: REPLACE eski satırı SİLER!
-- Belirtmediğin sütunlar DEFAULT değer alır, eski değerleri kaybedersin
REPLACE INTO customers (customer_id, first_name, last_name, email)
VALUES (1, 'Ahmet', 'Yılmaz', 'ahmet@email.com');
-- phone → NULL oldu (eski telefon numarası kayboldu!)
-- city → NULL oldu (eski şehir bilgisi kayboldu!)
-- registration_date → bugünün tarihi oldu (eski kayıt tarihi kayboldu!)REPLACE'in sorunları:
Tüm sütunları belirtmelisin — belirtmediklerin default değer alır
AUTO_INCREMENT değişir — yeni bir satır eklendiği için yeni ID alır (PK explicitly verilmezse)
ON DELETE trigger'ları tetiklenir — çünkü gerçekten silme yapıyor
Foreign key CASCADE tetiklenir — child tablolardaki ilişkili kayıtlar silinebilir!
Performans — DELETE + INSERT her zaman sadece UPDATE'ten yavaştır
⚠️ Dikkat:
REPLACEeski satırı gerçekten sildiği için, o satıra foreign key ile bağlı child kayıtlarON DELETE CASCADEvarsa silinir! Bu çok tehlikeli bir yan etkidir. GenellikleINSERT ... ON DUPLICATE KEY UPDATEtercih edilir.
INSERT ... ON DUPLICATE KEY UPDATE — Gerçek UPSERT
Bu MySQL'in doğru UPSERT çözümüdür. Çakışma varsa sil-ekle yerine güncelle.
Sözdizimi
INSERT INTO tablo_adi (sutun1, sutun2, ...)
VALUES (deger1, deger2, ...)
ON DUPLICATE KEY UPDATE
sutun1 = yeni_deger1,
sutun2 = yeni_deger2;Nasıl Çalışır?
INSERT yapmaya çalışır
PRIMARY KEY veya UNIQUE KEY çakışması yoksa → normal INSERT
Çakışma varsa →
ON DUPLICATE KEY UPDATEkısmındaki güncellemeleri yapar
-- Ürün fiyat güncelleme: varsa fiyatı güncelle, yoksa ekle
INSERT INTO products (product_id, product_name, category_id, price, stock_quantity)
VALUES (1, 'MacBook Pro 14"', 2, 52999.99, 25)
ON DUPLICATE KEY UPDATE
price = 52999.99,
stock_quantity = 25;
-- product_id = 1 zaten var → fiyat ve stok güncellendi
-- product_id = 1 yoksa → yeni ürün eklenirVALUES() Fonksiyonu (MySQL 8.0.19 öncesi)
INSERT'e verilen değerlere ON DUPLICATE KEY UPDATE kısmında referans verebilirsin:
-- MySQL 8.0.19 öncesi syntax
INSERT INTO products (product_id, product_name, price, stock_quantity)
VALUES (1, 'MacBook Pro 14"', 52999.99, 30)
ON DUPLICATE KEY UPDATE
price = VALUES(price), -- INSERT'teki price değeri
stock_quantity = VALUES(stock_quantity); -- INSERT'teki stock değeriAlias Syntax (MySQL 8.0.19+)
-- MySQL 8.0.19+ yeni syntax (önerilen)
INSERT INTO products (product_id, product_name, price, stock_quantity)
VALUES (1, 'MacBook Pro 14"', 52999.99, 30)
AS new_values
ON DUPLICATE KEY UPDATE
price = new_values.price,
stock_quantity = new_values.stock_quantity;Çoklu UPSERT
Birden fazla satırı tek seferde UPSERT edebilirsin:
-- Tedarikçi fiyat listesi güncellemesi
INSERT INTO products (product_id, product_name, price, stock_quantity)
VALUES
(1, 'MacBook Pro 14"', 52999.99, 25),
(2, 'iPhone 15 Pro', 62999.99, 120),
(3, 'Samsung Galaxy S24', 42999.99, 80),
(99, 'Yeni Ürün', 1999.99, 50)
ON DUPLICATE KEY UPDATE
price = VALUES(price),
stock_quantity = VALUES(stock_quantity),
updated_at = NOW();
-- product_id 1, 2, 3 → güncellendi
-- product_id 99 → yeni eklendiKoşullu Güncelleme
-- Stoku sadece artıyorsa güncelle (azaltma)
INSERT INTO products (product_id, product_name, price, stock_quantity)
VALUES (5, 'Kablosuz Mouse', 319.99, 600)
ON DUPLICATE KEY UPDATE
price = VALUES(price),
stock_quantity = GREATEST(stock_quantity, VALUES(stock_quantity));
-- Stok 500 ise ve yeni değer 600 ise → 600 olur
-- Stok 500 ise ve yeni değer 400 ise → 500 kalır (azalmaz)Sayaç Artırma Pattern'ı
-- Ürün görüntülenme sayısını artır
CREATE TABLE product_views (
product_id INT UNSIGNED PRIMARY KEY,
view_count INT UNSIGNED DEFAULT 0,
last_viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Her görüntülemede: varsa sayacı artır, yoksa oluştur
INSERT INTO product_views (product_id, view_count, last_viewed_at)
VALUES (5, 1, NOW())
ON DUPLICATE KEY UPDATE
view_count = view_count + 1,
last_viewed_at = NOW();
-- İlk görüntüleme: view_count = 1
-- İkinci görüntüleme: view_count = 2
-- ...Bu pattern, analytics ve sayaç tablolarında çok yaygındır.
REPLACE vs ON DUPLICATE KEY UPDATE
| Özellik | REPLACE | ON DUPLICATE KEY UPDATE |
|---|---|---|
| Mekanizma | DELETE + INSERT | UPDATE |
| AUTO_INCREMENT | Yeni ID alır | Eski ID korunur |
| Belirtilmeyen sütunlar | DEFAULT değer alır (eski değer kaybolur!) | Değişmez (korunur) |
| DELETE trigger | ✅ Tetiklenir | ❌ Tetiklenmez |
| CASCADE etkisi | ✅ Child kayıtlar silinebilir | ❌ Yan etki yok |
| Performans | Daha yavaş (DELETE + INSERT) | Daha hızlı (UPDATE) |
| Tavsiye | ❌ Genellikle kaçın | ✅ Tercih et |
-- ❌ REPLACE — tehlikeli, eski veriler kaybolabilir
REPLACE INTO customers (customer_id, first_name, last_name, email)
VALUES (1, 'Ahmet', 'Yılmaz', 'ahmet@email.com');
-- phone, city, registration_date kayboldu!
-- ✅ ON DUPLICATE KEY UPDATE — güvenli, sadece istenen sütunlar güncellenir
INSERT INTO customers (customer_id, first_name, last_name, email)
VALUES (1, 'Ahmet', 'Yılmaz', 'ahmet@email.com')
ON DUPLICATE KEY UPDATE
first_name = VALUES(first_name),
last_name = VALUES(last_name);
-- phone, city, registration_date korundu!💡 İpucu: Neredeyse her durumda
INSERT ... ON DUPLICATE KEY UPDATEtercih et.REPLACEyalnızca çok spesifik durumlarda (tüm sütunları zaten biliyorsan ve yan etkiler önemsizse) kullanılabilir.
PostgreSQL Karşılığı: ON CONFLICT
📝 PostgreSQL Notu: PostgreSQL'de UPSERT,
ON CONFLICTsyntax'ıyla yapılır: ``sql -- PostgreSQL UPSERT INSERT INTO products (product_id, product_name, price) VALUES (1, 'MacBook Pro', 52999.99) ON CONFLICT (product_id) DO UPDATE SET price = EXCLUDED.price, updated_at = NOW(); -- Çakışma varsa hiçbir şey yapma INSERT INTO products (product_id, product_name, price) VALUES (1, 'MacBook Pro', 52999.99) ON CONFLICT (product_id) DO NOTHING;``
Gerçek Dünya Örneği — Tedarikçi Stok Güncellemesi
-- Senaryo: Tedarikçi her gece ürün listesi gönderiyor
-- Bazı ürünler yeni, bazıları güncellenmeli
-- Geçici tabloya tedarikçi verisini yükle
CREATE TEMPORARY TABLE supplier_products (
sku VARCHAR(50) PRIMARY KEY,
product_name VARCHAR(200),
supplier_price DECIMAL(10,2),
available_stock INT UNSIGNED
);
INSERT INTO supplier_products VALUES
('SKU-001', 'MacBook Pro 14"', 48000.00, 30),
('SKU-002', 'iPhone 15 Pro', 58000.00, 150),
('SKU-NEW', 'AirPods Pro 3', 9999.99, 500);
-- Ana ürün tablosuna UPSERT
-- (Burada sku sütununun products tablosunda da olduğunu varsayıyoruz)
INSERT INTO products (product_name, price, stock_quantity)
SELECT sp.product_name, sp.supplier_price * 1.20, sp.available_stock
FROM supplier_products sp
ON DUPLICATE KEY UPDATE
price = VALUES(price),
stock_quantity = VALUES(stock_quantity),
updated_at = NOW();
-- Geçici tabloyu temizle
DROP TEMPORARY TABLE supplier_products;Sıkça Yapılan Hatalar
REPLACE'in veri kaybı riski: REPLACE eski satırı tamamen siler. Belirtmediğin sütunların eski değerleri kaybolur. Neredeyse her zaman
ON DUPLICATE KEY UPDATEtercih et.REPLACE ile CASCADE felaketi: REPLACE eski satırı sildiğinde,
ON DELETE CASCADEolan child tablolardaki tüm ilişkili kayıtlar da silinir. Müşteri bilgisini REPLACE ile güncellerken tüm siparişleri silebilirsin!ON DUPLICATE KEY UPDATE'te yanlış sütun: Sadece güncellemek istediğin sütunları
UPDATEkısmına yaz. Her sütunu yazmak gereksiz ve hata riski taşır.UNIQUE KEY olmadan UPSERT: ON DUPLICATE KEY UPDATE, PRIMARY KEY veya UNIQUE KEY çakışması gerektirir. Hiçbir unique constraint yoksa her INSERT her zaman yeni satır ekler — "update" kısmı asla çalışmaz.
VALUES() fonksiyonunun deprecation'ı: MySQL 8.0.20'de
VALUES()fonksiyonu deprecated oldu. Yeni projeler için alias syntax (AS new_values) kullan.
Özet
REPLACE varsa siler + yeni ekler, yoksa ekler — tehlikeli, veri kaybı riski var
INSERT ... ON DUPLICATE KEY UPDATE varsa günceller, yoksa ekler — güvenli, önerilen
UPSERT PRIMARY KEY veya UNIQUE KEY çakışmasına dayanır
REPLACEDELETE trigger'ları ve CASCADE'leri tetikler — yan etkilere dikkatON DUPLICATE KEY UPDATEsadece güncelleme yapar — yan etki yokSayaç artırma, fiyat güncelleme, stok senkronizasyonu gibi senaryolarda yaygın kullanılır
PostgreSQL'deki karşılığı
INSERT ... ON CONFLICT ... DO UPDATE/DO NOTHINGNeredeyse her zaman
ON DUPLICATE KEY UPDATEtercih et
AI Asistan
Sorularını yanıtlamaya hazır