← Kursa Dön
📄 Text · 30 min

Subquery Best Practices ve Performans

Giriş — Doğru Aracı Doğru Yerde Kullan

Subquery güçlü bir araçtır ama her yerde kullanmak doğru değildir. Bu derste subquery kullanırken dikkat edilmesi gereken performans kurallarını, anti-pattern'leri ve optimizasyon tekniklerini göreceğiz.


Kural 1: JOIN Mümkünse JOIN Tercih Et

-- ❌ Correlated subquery — N kez çalışır
SELECT product_name, 
    (SELECT category_name FROM categories WHERE category_id = p.category_id) AS category
FROM products p;

-- ✅ JOIN — tek geçişte çözer
SELECT p.product_name, c.category_name
FROM products p
LEFT JOIN categories c ON p.category_id = c.category_id;

Kural 2: SELECT'te Çoklu Subquery'den Kaçın

-- ❌ Her müşteri için 3 ayrı subquery = 3N sorgu
SELECT c.first_name,
    (SELECT COUNT(*) FROM orders WHERE customer_id = c.customer_id) AS cnt,
    (SELECT SUM(total_amount) FROM orders WHERE customer_id = c.customer_id) AS total,
    (SELECT MAX(order_date) FROM orders WHERE customer_id = c.customer_id) AS last_order
FROM customers c;

-- ✅ Tek LEFT JOIN + GROUP BY = tek geçiş
SELECT c.first_name,
    COUNT(o.order_id) AS cnt,
    COALESCE(SUM(o.total_amount), 0) AS total,
    MAX(o.order_date) AS last_order
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.first_name;

Kural 3: NOT IN Yerine NOT EXISTS

-- ⚠️ NOT IN — NULL tuzağı var
SELECT * FROM customers
WHERE customer_id NOT IN (SELECT customer_id FROM orders);
-- Eğer orders'ta customer_id NULL varsa → boş sonuç!

-- ✅ NOT EXISTS — güvenli ve genellikle daha hızlı
SELECT * FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);

Kural 4: Subquery'de Index Kullanımına Dikkat Et

-- Subquery sonucunda index yoktur — büyük sonuçlarda yavaş
WHERE customer_id IN (SELECT customer_id FROM large_temp_table);
-- large_temp_table'da index yoksa full scan

-- Çözüm: Geçici tablo oluştur ve index ekle, veya JOIN kullan

Kural 5: Derived Table'ı Materialized Etme

MySQL 8.0'da optimizer derived table'ı bazen materialize eder (geçici tablo oluşturur) bazen merge eder (dış sorguya birleştirir). EXPLAIN ile kontrol et:

EXPLAIN SELECT * FROM (
    SELECT category_id, AVG(price) AS avg_price
    FROM products GROUP BY category_id
) AS cat_avg WHERE avg_price > 1000;

Subquery vs JOIN Karar Ağacı

İki tablodan sütun gerekiyor mu?
├── EVET → JOIN
└── HAYIR
    ├── Varlık/yokluk kontrolü mü?
    │   ├── EVET → EXISTS / NOT EXISTS
    │   └── HAYIR
    │       ├── Aggregate karşılaştırma mı?
    │       │   ├── EVET → Subquery (WHERE'da)
    │       │   └── HAYIR → JOIN veya IN
    │       └── Ara hesaplama gerekli mi?
    │           ├── EVET → Derived table veya CTE
    │           └── HAYIR → En basit yöntemi seç

EXPLAIN ile Subquery Performansını Kontrol Et

EXPLAIN SELECT product_name FROM products
WHERE category_id IN (SELECT category_id FROM categories WHERE parent_category_id = 1);

-- type: eq_ref → iyi (index kullanılıyor)
-- type: ALL → kötü (full table scan)
-- Extra: "Using subquery" → subquery materialize edildi

Özet

  • JOIN mümkünse subquery yerine JOIN kullan

  • SELECT'te çoklu correlated subquery yerine LEFT JOIN + GROUP BY

  • NOT IN yerine NOT EXISTS (NULL-safe, genellikle daha hızlı)

  • Derived table'lar karmaşık hesaplamalar için iyi ama CTE daha okunabilir

  • EXPLAIN ile performansı kontrol et

  • Modern MySQL optimizer birçok subquery'yi otomatik optimize eder ama yine de doğru yazmak önemli