Teknik rehber

MySQL İndeks Seçimi ve EXPLAIN ile Sorgu Planı Okuma: Uygulamalı Rehber

MySQL'de doğru indeksleri seçmek ve EXPLAIN çıktısını yorumlamak, sorgu performansını doğrudan etkiler. Bu rehberde indeks türlerini, seçim stratejilerini ve EXPLAIN analizini adım adım ele alıyoruz.

Veritabanı ve sunucu altyapısı görseli
Görsel: Unsplash · Görsel kaynağı
MySQL İndeks Seçimi ve EXPLAIN ile Sorgu Planı Okuma

Veritabanı performansı, büyük ölçüde doğru indekslerin seçilmesine ve sorgu planlarının doğru okunmasına bağlıdır. Yanlış indeksler yalnızca disk alanı israf etmez; aynı zamanda yazma maliyetini artırır ve optimizasyonu zorlaştırır. Bu yazıda, MySQL'de indeks seçiminin temel prensiplerini, EXPLAIN çıktısının nasıl yorumlanacağını ve uygulanabilir bir örnek üzerinden adım adım analiz yapmayı ele alacağız.

İndeks Nedir ve Neden Önemlidir?

İndeks, bir tablodaki satırlara hızlı erişim sağlamak için kullanılan bir veri yapısıdır. MySQL'de en yaygın indeks türü B-Tree'dir. İndeksler, WHERE, JOIN, ORDER BY ve GROUP BY gibi işlemlerde tarama maliyetini düşürür. Ancak her sütuna indeks eklemek doğru bir yaklaşım değildir; çünkü her INSERT, UPDATE ve DELETE işlemi indekslerin de güncellenmesini gerektirir.

İndeks Türleri

  • B-Tree indeks: Eşitlik ve aralık sorgularında kullanılır. Varsayılan indeks türüdür.
  • Hash indeks: Yalnızca eşitlik karşılaştırmalarında etkilidir; MEMORY motorunda kullanılır.
  • Full-text indeks: Metin aramaları için kullanılır.
  • Bileşik indeks: Birden fazla sütunu kapsar; sıralama önemlidir.

İndeks Seçim Stratejileri

İndeks seçerken şu soruları sormalısınız:

  1. Sorguda hangi sütunlar WHERE koşulunda kullanılıyor?
  2. Bu sütunlar arasında seçicilik (selectivity) yüksek olan hangisi?
  3. Sorgu ORDER BY veya GROUP BY içeriyor mu?
  4. Bileşik indeks gerekiyorsa sütun sıralaması ne olmalı?

Bileşik indekslerde en soldaki sütun (leftmost prefix) kuralı geçerlidir. Örneğin (a, b, c) indeksi, a, (a, b) ve (a, b, c) koşullarında kullanılabilir; ancak yalnızca b veya c için kullanılamaz.

Örnek Tablo ve Sorgu

Aşağıdaki gibi bir orders tablosu düşünelim:

CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  customer_id INT NOT NULL,
  status VARCHAR(20) NOT NULL,
  created_at DATETIME NOT NULL,
  total DECIMAL(10,2) NOT NULL,
  INDEX idx_customer_status (customer_id, status),
  INDEX idx_created_at (created_at)
);

Şu sorguyu çalıştıralım:

SELECT id, total
FROM orders
WHERE customer_id = 42
  AND status = 'shipped'
ORDER BY created_at DESC
LIMIT 10;

Bu sorgu için EXPLAIN çıktısını inceleyelim:

EXPLAIN SELECT id, total
FROM orders
WHERE customer_id = 42
  AND status = 'shipped'
ORDER BY created_at DESC
LIMIT 10;

EXPLAIN Çıktısını Okuma

EXPLAIN çıktısındaki temel sütunlar şunlardır:

  • id: Sorgu bloğunun kimliği.
  • select_type: Sorgu türü (SIMPLE, PRIMARY, SUBQUERY vb.).
  • table: Erişilen tablo.
  • type: Erişim türü. ALL (tam tablo taraması) en kötü, const en iyidir. ref, range, index ara değerlerdir.
  • possible_keys: Kullanılabilecek indeksler.
  • key: Gerçekte kullanılan indeks.
  • key_len: Kullanılan indeks anahtarının uzunluğu.
  • ref: İndeksle karşılaştırılan sütun veya sabit.
  • rows: Taranması tahmin edilen satır sayısı.
  • filtered: Koşul sonrası kalan satır yüzdesi.
  • Extra: Ek bilgiler. Using index (covering index), Using where, Using filesort, Using temporary gibi ifadeler önemlidir.

Yukarıdaki sorgu için ideal senaryoda key olarak idx_customer_status kullanılmalı, type ref olmalı ve Extra alanında Using where ile birlikte Using filesort görülmemelidir. Eğer Using filesort varsa, ORDER BY için ayrı bir indeks gerekebilir. Bu durumda (customer_id, status, created_at) şeklinde bir bileşik indeks, hem filtreleme hem sıralamayı tek indeksle çözebilir.

Covering Index Kullanımı

Sorguda seçilen sütunlar (id, total) indekste yer almıyorsa, MySQL her satır için tabloya geri döner (random I/O). Eğer indeks tüm ihtiyaç duyulan sütunları içeriyorsa buna covering index denir ve Extra alanında Using index görülür. Örneğin:

ALTER TABLE orders
ADD INDEX idx_covering (customer_id, status, created_at, total);

Bu indeks ile SELECT id, total sorgusu id birincil anahtar olduğu için InnoDB'de indeks içinde dolaylı olarak bulunur; total ise indekste yer alır. Böylece tabloya erişim olmadan sonuç üretilebilir.

Uygulanabilir Kod Örneği: Laravel ile EXPLAIN Analizi

Laravel kullanıyorsanız, DB::select ile EXPLAIN çalıştırabilir ve sonucu loglayabilirsiniz. Aşağıdaki örnek, bir sorgunun planını alıp key ve type değerlerini kontrol eder:

use Illuminate\Support\Facades\DB;

$sql = 'SELECT id, total FROM orders WHERE customer_id = ? AND status = ? ORDER BY created_at DESC LIMIT 10';
$bindings = [42, 'shipped'];

$explain = DB::select('EXPLAIN ' . $sql, $bindings);

foreach ($explain as $row) {
    if ($row->type === 'ALL') {
        logger()->warning('Tam tablo taraması tespit edildi', (array) $row);
    }
    if (str_contains($row->Extra ?? '', 'Using filesort')) {
        logger()->info('Filesort kullanılıyor, indeks optimizasyonu gerekebilir', (array) $row);
    }
}

Bu yaklaşım, üretim ortamında yavaş sorguları tespit etmek ve indeks iyileştirmelerini doğrulamak için kullanılabilir. Ayrıca MySQL'in performance_schema ve sys şeması, daha derin analiz için kullanılabilir.

Sonuç

MySQL'de indeks seçimi ve EXPLAIN analizi, performans optimizasyonunun temel taşlarıdır. Doğru indeks, sorgunun type değerini ALL'dan ref'e çekebilir, Using filesort ve Using temporary gibi ek maliyetleri ortadan kaldırabilir. Bileşik indekslerde sütun sırasına dikkat etmek, covering index kullanmak ve düzenli olarak EXPLAIN ile planları doğrulamak, uygulamanızın ölçeklenebilirliğini artırır. Unutmayın: her indeks bir maliyettir; bu nedenle yalnızca gerçekten ihtiyaç duyulan indeksleri oluşturun ve etkilerini ölçün.

Resmi Kaynaklar:

🍪
Çerez Kullanımı

Kodumun Hatası, web sitesinin çalışmasını sağlamak ve kullanıcı deneyimini geliştirmek için çerezler kullanabilir. Zorunlu olmayan çerezler için tercihlerinizi belirleyebilir, ayrıntılı bilgiye Çerez Politikamızdan ulaşabilirsiniz.