Veritabanı & DevOps

MySQL Performans Optimizasyonu: Index ve Query Tuning

Fatih Algül
20.03.2026 338 görüntülenme

MySQL'de Performans Neden Önemlidir?

Bir uygulama geliştirirken en sık karşılaşılan darboğazlardan biri veritabanı performansıdır. Küçük veri setlerinde sorunsuz çalışan sorgular, tablolar büyüdükçe saniyeler hatta dakikalar süren yanıt sürelerine neden olabilir. MySQL performans optimizasyonu, doğru index stratejileri ve query tuning teknikleriyle bu sorunların önüne geçmenizi sağlar. Bu yazıda, MySQL'de performansı artırmanın temel yollarını detaylı şekilde ele alacağız.

Index (Dizin) Nedir ve Nasıl Çalışır?

Index, veritabanındaki verilere daha hızlı erişim sağlamak için oluşturulan veri yapılarıdır. Bir kitabın sonundaki dizin gibi düşünebilirsiniz: tüm sayfaları tek tek okumak yerine, aradığınız konuyu dizinden bulup doğrudan ilgili sayfaya gidersiniz. MySQL'de varsayılan index yapısı B-Tree (Balanced Tree) algoritmasına dayanır ve logaritmik zaman karmaşıklığında arama yapmanıza olanak tanır.

Index Türleri

  • PRIMARY KEY: Tablodaki her satırı benzersiz şekilde tanımlar. Otomatik olarak clustered index oluşturur ve veriler fiziksel olarak bu indexe göre sıralanır.
  • UNIQUE INDEX: Tekrarlanan değerlere izin vermez. E-posta adresi veya kullanıcı adı gibi alanlar için idealdir.
  • INDEX (Non-Unique): Tekrarlanan değerlere izin veren standart indextir. Sorgularda sık filtrelenen sütunlar için kullanılır.
  • COMPOSITE INDEX: Birden fazla sütun üzerinde oluşturulan indextir. Sıralama son derece önemlidir.
  • FULLTEXT INDEX: Metin tabanlı aramalarda kullanılır. LIKE '%kelime%' yerine çok daha performanslı metin araması sağlar.
  • SPATIAL INDEX: Coğrafi veri tipleri (POINT, POLYGON) için kullanılır.

Index Oluşturma Örnekleri

Aşağıda sık kullanılan index oluşturma senaryolarını görebilirsiniz:

-- Tek sütunlu index
CREATE INDEX idx_users_email ON users(email);

-- Composite (bileşik) index
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);

-- Unique index
CREATE UNIQUE INDEX idx_users_username ON users(username);

-- Prefix index (uzun metin alanları için)
CREATE INDEX idx_posts_title ON posts(title(50));

-- Mevcut tabloya ALTER ile ekleme
ALTER TABLE products ADD INDEX idx_category (category_id);

Composite Index ve Leftmost Prefix Kuralı

Composite indexler, MySQL optimizasyonunun en kritik konularından biridir. Burada anlaşılması gereken en önemli kavram leftmost prefix kuralıdır. MySQL, composite bir indexi yalnızca soldan sağa doğru kullanabilir.

Örneğin (a, b, c) sütunlarından oluşan bir composite indexiniz varsa:

  • WHERE a = 1 → Index kullanılır
  • WHERE a = 1 AND b = 2 → Index kullanılır
  • WHERE a = 1 AND b = 2 AND c = 3 → Index tam olarak kullanılır
  • WHERE b = 2 → Index kullanılmaz
  • WHERE b = 2 AND c = 3 → Index kullanılmaz
  • WHERE a = 1 AND c = 3 → Yalnızca a kısmı kullanılır

Bu nedenle composite index oluştururken sütun sıralaması büyük önem taşır. Genel kural olarak kardinalitesi yüksek (benzersiz değer sayısı fazla olan) sütunları öne koymak iyi bir başlangıçtır, ancak sorgu desenlerinizi analiz etmek her zaman daha doğru sonuç verir.

EXPLAIN ile Sorgu Analizi

Sorgu optimizasyonunun ilk adımı, MySQL'in sorgunuzu nasıl çalıştırdığını anlamaktır. EXPLAIN komutu bu konuda en güçlü aracınızdır.

EXPLAIN SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.city = 'Istanbul'
AND o.created_at > '2025-01-01'
ORDER BY o.total DESC;

EXPLAIN çıktısında dikkat etmeniz gereken başlıca sütunlar:

  • type: Erişim tipini gösterir. ALL (full table scan) en kötüsü, const ve eq_ref en iyisidir. Sıralama: const > eq_ref > ref > range > index > ALL
  • possible_keys: Kullanılabilecek indexleri listeler.
  • key: Gerçekte kullanılan indexi gösterir. NULL ise hiçbir index kullanılmamıştır.
  • rows: MySQL'in tahmini olarak tarayacağı satır sayısı. Ne kadar düşükse o kadar iyidir.
  • Extra: Using filesort ve Using temporary gibi uyarılar performans sorunlarına işaret eder. Using index ise covering index kullanıldığını gösterir ve idealdir.

MySQL 8.0 ve sonrasında EXPLAIN ANALYZE komutuyla gerçek çalışma süreleri de dahil daha detaylı bilgi alabilirsiniz:

EXPLAIN ANALYZE SELECT * FROM orders
WHERE user_id = 42 AND status = 'completed';

Query Tuning: Sık Yapılan Hatalar ve Çözümleri

1. SELECT * Kullanımından Kaçının

Tüm sütunları çekmek hem ağ trafiğini artırır hem de covering index kullanımını engeller. Yalnızca ihtiyacınız olan sütunları seçin:

-- Kötü
SELECT * FROM users WHERE city = 'Ankara';

-- İyi
SELECT id, name, email FROM users WHERE city = 'Ankara';

2. Index Kırıcı Fonksiyon Kullanımı

WHERE koşulundaki sütunlara fonksiyon uygulamak indexin kullanılmasını engeller:

-- Kötü: Index kullanılmaz
SELECT * FROM orders WHERE YEAR(created_at) = 2025;

-- İyi: Index kullanılır
SELECT * FROM orders
WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01';

-- Kötü: Index kullanılmaz
SELECT * FROM users WHERE LOWER(email) = 'info@example.com';

-- İyi (MySQL 8.0+): Functional index kullanın
CREATE INDEX idx_email_lower ON users((LOWER(email)));

3. LIKE Sorgularında Wildcard Kullanımı

Başta wildcard kullanmak index taramasını engeller:

-- Kötü: Full table scan
SELECT * FROM products WHERE name LIKE '%telefon%';

-- İyi: Index kullanılabilir
SELECT * FROM products WHERE name LIKE 'telefon%';

-- Alternatif: FULLTEXT index kullanın
ALTER TABLE products ADD FULLTEXT INDEX ft_name (name);
SELECT * FROM products WHERE MATCH(name) AGAINST('telefon');

4. OR Yerine UNION veya IN Kullanımı

-- Verimsiz olabilir
SELECT * FROM users WHERE city = 'Istanbul' OR city = 'Ankara';

-- Daha iyi: IN kullanımı
SELECT * FROM users WHERE city IN ('Istanbul', 'Ankara');

5. OFFSET ile Sayfalama Problemi

Büyük OFFSET değerleri ciddi performans sorunlarına yol açar çünkü MySQL atlanan satırları da okumak zorundadır:

-- Kötü: 100.000 satır okunup atılır
SELECT * FROM posts ORDER BY id LIMIT 10 OFFSET 100000;

-- İyi: Keyset pagination (cursor-based)
SELECT * FROM posts WHERE id > 100000 ORDER BY id LIMIT 10;

Covering Index Stratejisi

Bir sorgunun ihtiyaç duyduğu tüm sütunlar indekste bulunuyorsa, MySQL tabloya hiç erişmeden sonucu doğrudan indexten döndürebilir. Buna covering index denir ve EXPLAIN çıktısında Using index ifadesiyle görülür.

-- Sorgu: Belirli bir şehirdeki kullanıcıların ad ve e-postalarını getir
SELECT name, email FROM users WHERE city = 'Izmir';

-- Covering index oluşturma
CREATE INDEX idx_city_name_email ON users(city, name, email);

Bu yaklaşım özellikle yoğun okunan tablolarda dramatik performans artışı sağlar, ancak index boyutunu ve yazma maliyetini artıracağını unutmayın.

Slow Query Log ile Sorunlu Sorguları Tespit Etme

MySQL'in slow query log özelliğini aktif ederek belirlediğiniz eşiği aşan sorguları otomatik olarak kaydedebilirsiniz:

-- Slow query log'u aktifleştirme
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 1 saniyeden uzun süren sorgular
SET GLOBAL log_queries_not_using_indexes = 'ON';

-- Log dosyasının konumunu öğrenme
SHOW VARIABLES LIKE 'slow_query_log_file';

Toplanan logları analiz etmek için MySQL ile birlikte gelen mysqldumpslow aracını veya Percona'nın pt-query-digest aracını kullanabilirsiniz:

# En yavaş 10 sorguyu listele
mysqldumpslow -t 10 /var/log/mysql/slow.log

# Daha detaylı analiz
pt-query-digest /var/log/mysql/slow.log

InnoDB Buffer Pool Optimizasyonu

InnoDB storage engine'in en kritik yapılandırma parametresi innodb_buffer_pool_size değeridir. Bu, verilerin ve indexlerin bellekte tutulduğu alanı belirler. Genel kural olarak sunucunun toplam RAM'inin %60-80'i kadar ayarlanması önerilir:

-- Mevcut değeri kontrol etme
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';

-- Buffer pool kullanım oranını kontrol etme
SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests';
SHOW STATUS LIKE 'Innodb_buffer_pool_reads';

Innodb_buffer_pool_reads değerinin Innodb_buffer_pool_read_requests değerine oranı ne kadar düşükse, verileriniz o kadar iyi bellekte tutuluyor demektir. Bu oranın %1'in altında olması hedeflenmelidir.

Gereksiz Indexleri Tespit Etme ve Temizleme

Her index yazma işlemlerinde (INSERT, UPDATE, DELETE) ek maliyet getirir. Kullanılmayan indexleri tespit edip kaldırmak da önemli bir optimizasyon adımıdır:

-- MySQL 8.0+: Kullanılmayan indexleri tespit etme
SELECT object_schema, object_name, index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND count_star = 0
AND object_schema = 'your_database'
ORDER BY object_name, index_name;

-- Duplicate indexleri bulma
SELECT table_name, index_name, GROUP_CONCAT(column_name ORDER BY seq_in_index)
FROM information_schema.statistics
WHERE table_schema = 'your_database'
GROUP BY table_name, index_name;

Özet: Performans Kontrol Listesi

  1. Slow query log'u aktif edin ve düzenli olarak analiz edin.
  2. Tüm kritik sorguları EXPLAIN ile inceleyin; type: ALL görürseniz index eklemeyi düşünün.
  3. WHERE, JOIN ve ORDER BY sütunlarına uygun index oluşturun.
  4. Composite indexlerde leftmost prefix kuralını unutmayın.
  5. WHERE koşullarında sütunlara fonksiyon uygulamaktan kaçının.
  6. SELECT * yerine yalnızca gerekli sütunları seçin.
  7. Büyük tablolarda OFFSET yerine keyset pagination kullanın.
  8. Mümkün olduğunca covering index stratejisinden yararlanın.
  9. Buffer pool boyutunu sunucu RAM'ine göre optimize edin.
  10. Kullanılmayan ve duplike indexleri düzenli olarak temizleyin.

Performans optimizasyonu sürekli bir süreçtir. Uygulamanızın sorgu desenleri değiştikçe index stratejinizi de güncellemeniz gerekecektir. Düzenli izleme ve analiz yaparak veritabanınızın her zaman en iyi performansta çalışmasını sağlayabilirsiniz.

Yazar Hakkında
Fatih Algül
TechSoft Solutions
Proje mi var?

Yazılım, IoT veya otomasyon konularında destek almak ister misiniz?

İletişime Geç