← Kursa Dön
📄 Text · 25 min

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?

  1. INSERT yapmaya çalışır

  2. Eğer PRIMARY KEY veya UNIQUE KEY çakışması yoksa → normal INSERT gibi ekler

  3. 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ı:

  1. Tüm sütunları belirtmelisin — belirtmediklerin default değer alır

  2. AUTO_INCREMENT değişir — yeni bir satır eklendiği için yeni ID alır (PK explicitly verilmezse)

  3. ON DELETE trigger'ları tetiklenir — çünkü gerçekten silme yapıyor

  4. Foreign key CASCADE tetiklenir — child tablolardaki ilişkili kayıtlar silinebilir!

  5. Performans — DELETE + INSERT her zaman sadece UPDATE'ten yavaştır

⚠️ Dikkat: REPLACE eski satırı gerçekten sildiği için, o satıra foreign key ile bağlı child kayıtlar ON DELETE CASCADE varsa silinir! Bu çok tehlikeli bir yan etkidir. Genellikle INSERT ... ON DUPLICATE KEY UPDATE tercih 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?

  1. INSERT yapmaya çalışır

  2. PRIMARY KEY veya UNIQUE KEY çakışması yoksa → normal INSERT

  3. Çakışma varsa → ON DUPLICATE KEY UPDATE kı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 eklenir

VALUES() 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ğeri

Alias 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 eklendi

Koş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

ÖzellikREPLACEON DUPLICATE KEY UPDATE
MekanizmaDELETE + INSERTUPDATE
AUTO_INCREMENTYeni ID alırEski ID korunur
Belirtilmeyen sütunlarDEFAULT 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
PerformansDaha 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 UPDATE tercih et. REPLACE yalnı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 CONFLICT syntax'ı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

  1. 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 UPDATE tercih et.

  2. REPLACE ile CASCADE felaketi: REPLACE eski satırı sildiğinde, ON DELETE CASCADE olan child tablolardaki tüm ilişkili kayıtlar da silinir. Müşteri bilgisini REPLACE ile güncellerken tüm siparişleri silebilirsin!

  3. ON DUPLICATE KEY UPDATE'te yanlış sütun: Sadece güncellemek istediğin sütunları UPDATE kısmına yaz. Her sütunu yazmak gereksiz ve hata riski taşır.

  4. 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.

  5. 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

  • REPLACE DELETE trigger'ları ve CASCADE'leri tetikler — yan etkilere dikkat

  • ON DUPLICATE KEY UPDATE sadece güncelleme yapar — yan etki yok

  • Sayaç artırma, fiyat güncelleme, stok senkronizasyonu gibi senaryolarda yaygın kullanılır

  • PostgreSQL'deki karşılığı INSERT ... ON CONFLICT ... DO UPDATE/DO NOTHING

  • Neredeyse her zaman ON DUPLICATE KEY UPDATE tercih et