İletişime geç

MySQL'de Yavaş Sorguları Bulmak: Slow Query Log ve EXPLAIN

Site yavaşladığında tahmin yürütmek yerine ölçmek gerekiyor. MySQL'in yavaş sorgu kaydını açmak, en pahalı sorguları bulmak ve EXPLAIN çıktısını okuyup doğru indeksi eklemek.

MySQL'de Yavaş Sorguları Bulmak: Slow Query Log ve EXPLAIN Veritabanı

Bir site yavaşladığında ilk tahmin genellikle sunucunun yetersiz olduğu yönünde olur. Benim tecrübemde ise vakaların büyük kısmında sorun, birkaç kötü yazılmış ya da indekssiz sorgu. Daha büyük bir sunucuya geçmeden önce bu sorguları bulmak hem daha ucuz hem daha kalıcı bir çözüm.

1. Yavaş sorgu kaydını açın

MySQL, belirli bir süreden uzun süren sorguları bir dosyaya yazabilir. Sunucuyu yeniden başlatmadan, çalışırken açmak mümkün:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5;          -- 0,5 saniyeden uzun sürenler
SET GLOBAL log_queries_not_using_indexes = 'ON';
SHOW VARIABLES LIKE 'slow_query_log_file'; -- kaydın yazıldığı dosya

Bu ayarlar MySQL yeniden başladığında sıfırlanır. Kalıcı olması için my.cnf dosyasının [mysqld] bölümüne ekleyin:

slow_query_log = 1
long_query_time = 0.5
slow_query_log_file = /var/lib/mysql/slow.log

log_queries_not_using_indexes ayarı küçük tablolarda da çok sayıda kayıt üretebilir. Birkaç gün inceleme yaptıktan sonra kapatmanızı öneririm.

2. En pahalı sorguları özetleyin

Log dosyası kısa sürede binlerce satıra ulaşır. MySQL ile birlikte gelen mysqldumpslow aracı, aynı kalıptaki sorguları gruplayıp toplam süreye göre sıralar:

mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log

-s t toplam süreye göre sıralar, -t 10 ilk on kalıbı gösterir. Burada önemli olan, en yavaş tek sorgu değil, toplamda en çok süre harcatan sorgu. Tek seferde 2 saniye süren bir rapor sorgusu, her sayfa açılışında 80 milisaniye harcayan bir sorgudan çok daha az sorun yaratır.

3. EXPLAIN ile sorgunun planına bakın

Sorunlu sorguyu bulduktan sonra başına EXPLAIN ekleyerek MySQL'in bu sorguyu nasıl çalıştırmayı planladığını görebilirsiniz. Örnek olarak bir blogun kategori sayfasındaki sorgu:

EXPLAIN SELECT id, title, slug, published_at
FROM posts
WHERE category_id = 4 AND is_published = 1
ORDER BY published_at DESC
LIMIT 12;
MySQL EXPLAIN çıktısının indeks eklemeden önce ve sonra karşılaştırması
Aynı sorgunun indeks eklemeden önceki ve sonraki planı.

Çıktıda dikkat edilecek sütunlar:

SütunNe anlatır?Kötü işaret
typeTabloya nasıl erişildiğiALL: tablonun tamamı taranıyor
keyKullanılan indeksNULL: hiçbir indeks kullanılmıyor
rowsOkunacağı tahmin edilen satır sayısıDönen satır sayısından çok büyük
ExtraEk işlemlerUsing filesort, Using temporary

Yukarıdaki sorgu için ilk çıktı type: ALL, key: NULL ve Using filesort gösteriyordu: 12 satır döndürmek için tablonun tamamı okunup sıralanıyor.

4. Doğru indeksi ekleyin

Bileşik (birden fazla sütunlu) indeks kurarken sıralama önemli. Genel kural: önce eşitlikle (=) filtrelenen sütunlar, sonra sıralama ya da aralık (>, BETWEEN) kullanılan sütun.

ALTER TABLE posts
  ADD INDEX idx_kategori_yayin (category_id, is_published, published_at);

İndeksten sonra aynı EXPLAIN çıktısında type: ref, key: idx_kategori_yayin ve çok daha küçük bir rows değeri görülür; Using filesort da kaybolur. Çünkü veriler indekste zaten tarih sırasına göre duruyor.

MySQL 8.0.18 ve sonrasında EXPLAIN ANALYZE komutu sorguyu gerçekten çalıştırıp her adımın ne kadar sürdüğünü gösterir. Tahmin ile gerçeğin ne kadar örtüştüğünü görmek için çok faydalı.

Sık karşılaşılan sebepler

  • Sütuna fonksiyon uygulamak: WHERE YEAR(published_at) = 2026 indeksi kullanamaz. Bunun yerine WHERE published_at >= '2026-01-01' AND published_at < '2027-01-01' yazın.
  • Başında joker karakter olan LIKE: LIKE '%kelime%' indeks kullanamaz. Ciddi arama ihtiyacı için FULLTEXT indeks ya da ayrı bir arama motoru düşünün.
  • Gereksiz SELECT *: Büyük metin sütunlarını listeleme sayfalarında çekmek hem diski hem belleği yorar.
  • N+1 sorgu: Tek tek çok sayıda küçük sorgu, slow log'a hiç düşmeden siteyi yavaşlatabilir. Laravel gibi ORM kullanan projelerde ilişkileri with() ile önceden yüklemek gerekiyor.
  • Karakter seti uyuşmazlığı: Farklı karakter setine sahip iki sütunu birleştirmek (JOIN) indeksin kullanılmasını engelleyebilir. Bu konuyu latin5'ten utf8mb4'e geçiş yazısında ayrıntılı anlattım.

İndeks her zaman iyi mi?

Hayır. Her indeks, INSERT ve UPDATE işlemlerini biraz yavaşlatır ve disk kaplar. Kullanılmayan indeksleri bulmak için MySQL 8'de sys.schema_unused_indexes görünümüne bakabilirsiniz. Amaç her sütuna indeks eklemek değil, en sık çalışan sorgulara uygun birkaç iyi indeks kurmak.