
Veritabanı İndeks Optimizasyonu: Yavaş Sorguları Hızlandırma
MySQL ve PostgreSQL veritabanlarında indeks oluşturma, optimize etme ve gereksiz indeksleri temizleme rehberi.
Veritabanı İndeks Optimizasyonu: Yavaş Sorguları Hızlandırma
Veritabanı performansının en kritik bileşeni indekslerdir. Doğru indeks stratejisi, saniyeler süren sorguları milisaniyeye indirebilir. Bu rehberde MySQL ve PostgreSQL için kapsamlı indeks optimizasyonu tekniklerini öğreneceksiniz.
İndeks Nedir?
İndeks, veritabanının belirli sütunlardaki verileri hızlıca bulmasını sağlayan bir veri yapısıdır. Kitabın sonundaki dizin gibi düşünebilirsiniz.
İndekssiz: Tüm satırları tara (Full Table Scan)
İndeksli: B-Tree üzerinden doğrudan ulaş (Index Seek)
1.000.000 satır tablosunda:
- Full Scan: ~500ms
- Index Seek: ~0.1ms
MySQL'de Yavaş Sorgu Analizi
Slow Query Log Aktifleştirme
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
# MySQL'i yeniden başlat
sudo systemctl restart mysql
# Slow query logunu izle
sudo tail -f /var/log/mysql/slow.log
EXPLAIN ile Sorgu Analizi
-- Yavaş sorguyu analiz et
EXPLAIN SELECT * FROM siparisler
WHERE musteri_id = 1234
AND durum = 'beklemede'
ORDER BY tarih DESC;
EXPLAIN çıktısını okuma:
+----+-------------+----------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | siparisler| ALL | NULL | NULL | NULL | 500000 | Using where |
type sütunu değerleri (en iyiden kötüye):
system/const→ Mükemmeleq_ref/ref→ Çok iyirange→ İyi (BETWEEN, IN, >, < için)index→ Orta (tüm indeks taranıyor)ALL→ Kötü! Full table scan
İndeks Oluşturma
-- Tekil indeks
CREATE INDEX idx_musteri_id ON siparisler(musteri_id);
-- Bileşik indeks (column order önemli!)
CREATE INDEX idx_musteri_durum ON siparisler(musteri_id, durum);
-- Kapsayıcı indeks (covering index)
CREATE INDEX idx_siparis_ozet ON siparisler(musteri_id, durum, tarih);
-- Benzersiz indeks
CREATE UNIQUE INDEX idx_email_unique ON kullanicilar(email);
-- FULLTEXT indeks
CREATE FULLTEXT INDEX idx_urun_ara ON urunler(ad, aciklama);
Bileşik İndeks Sıralaması
-- Kötü: WHERE koşulunda birinci sütun yok
CREATE INDEX idx_yanlis ON siparisler(durum, musteri_id);
SELECT * FROM siparisler WHERE musteri_id = 1234; -- İndeks kullanmaz!
-- Doğru: Önce en seçici sütun
CREATE INDEX idx_dogru ON siparisler(musteri_id, durum);
SELECT * FROM siparisler WHERE musteri_id = 1234; -- İndeks kullanır!
SELECT * FROM siparisler WHERE musteri_id = 1234 AND durum = 'beklemede'; -- İndeks kullanır!
Leftmost prefix kuralı: Bileşik indekste, WHERE koşulunuz en soldaki sütundan başlamalıdır.
PostgreSQL'de İndeks Analizi
EXPLAIN ANALYZE
-- Gerçek çalışma sürelerini göster
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM siparisler
WHERE musteri_id = 1234
AND durum = 'beklemede';
Seq Scan on siparisler (cost=0.00..15420.00 rows=12 width=156)
(actual time=0.042..234.567 rows=15 loops=1)
Filter: ((musteri_id = 1234) AND (durum = 'beklemede'))
Rows Removed by Filter: 499985
Planning Time: 0.3 ms
Execution Time: 234.6 ms
PostgreSQL İndeks Türleri
-- B-Tree (varsayılan, en yaygın)
CREATE INDEX idx_musteri ON siparisler(musteri_id);
-- Hash (eşitlik sorguları için)
CREATE INDEX idx_hash ON siparisler USING HASH(musteri_id);
-- GIN (JSON, tam metin arama için)
CREATE INDEX idx_gin ON belgeler USING GIN(icerik);
-- BRIN (büyük, sıralı veri için - zaman damgaları)
CREATE INDEX idx_brin ON loglar USING BRIN(log_tarihi);
-- Kısmi indeks (subset için)
CREATE INDEX idx_aktif ON siparisler(musteri_id)
WHERE durum = 'aktif';
-- İfade indeksi
CREATE INDEX idx_lower_email ON kullanicilar(LOWER(email));
Mevcut İndeksleri İnceleme
MySQL
-- Tablodaki indeksleri listele
SHOW INDEX FROM siparisler;
-- Kullanılmayan indeksleri bul (performance_schema)
SELECT
object_schema,
object_name,
index_name,
count_fetch,
count_insert,
count_update,
count_delete
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND count_star = 0
AND object_schema NOT IN ('mysql', 'information_schema')
ORDER BY object_schema, object_name;
PostgreSQL
-- Tablodaki indeksleri listele
SELECT
indexname,
indexdef,
pg_size_pretty(pg_relation_size(indexrelid)) AS boyut
FROM pg_indexes
WHERE tablename = 'siparisler';
-- Kullanılmayan indeksleri bul
SELECT
schemaname,
tablename,
indexname,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_boyutu,
idx_scan AS kullanim_sayisi
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;
İndeks Bakımı ve Optimizasyonu
MySQL
-- İndeks istatistiklerini güncelle
ANALYZE TABLE siparisler;
-- İndeksi yeniden oluştur (fragmantasyonu gider)
ALTER TABLE siparisler ENGINE=InnoDB;
-- Gereksiz indeksi sil
DROP INDEX idx_eski ON siparisler;
-- pt-duplicate-key-checker (Percona Toolkit)
pt-duplicate-key-checker --host=localhost --user=root --password=sifre
PostgreSQL
-- İstatistikleri güncelle
ANALYZE siparisler;
-- VACUUM + istatistik güncelle
VACUUM ANALYZE siparisler;
-- Bloat'lı indeksleri yeniden oluştur (kilitlemez)
REINDEX INDEX CONCURRENTLY idx_musteri_id;
-- Tüm indeksleri yeniden oluştur
REINDEX TABLE CONCURRENTLY siparisler;
Pratik Optimizasyon Senaryoları
Senaryo 1: JOIN Performansı
-- Yavaş JOIN sorgusu
SELECT u.ad, s.tutar, s.tarih
FROM kullanicilar u
JOIN siparisler s ON u.id = s.musteri_id
WHERE s.tarih > '2026-01-01';
-- Çözüm: Her iki JOIN sütununa indeks ekle
CREATE INDEX idx_siparis_musteri ON siparisler(musteri_id);
CREATE INDEX idx_siparis_tarih ON siparisler(tarih);
-- Veya bileşik:
CREATE INDEX idx_siparis_tarih_musteri ON siparisler(tarih, musteri_id);
Senaryo 2: ORDER BY + LIMIT Optimizasyonu
-- Yavaş sayfalama sorgusu
SELECT * FROM urunler ORDER BY fiyat DESC LIMIT 10 OFFSET 50000;
-- Çözüm: Kapsayıcı indeks + keyset pagination
CREATE INDEX idx_urun_fiyat ON urunler(fiyat DESC, id);
-- Keyset pagination
SELECT * FROM urunler
WHERE fiyat < 150 OR (fiyat = 150 AND id < 12345)
ORDER BY fiyat DESC, id DESC
LIMIT 10;
Senaryo 3: LIKE ile Arama
-- Bu indeks KULLANMAZ (%ile başlıyor)
SELECT * FROM urunler WHERE ad LIKE '%laptop%';
-- Bu indeks KULLANIR (sabit prefix)
SELECT * FROM urunler WHERE ad LIKE 'laptop%';
-- B-Tree indeks: CREATE INDEX idx_urun_ad ON urunler(ad);
-- Tam metin arama için FULLTEXT (MySQL)
CREATE FULLTEXT INDEX idx_urun_ft ON urunler(ad, aciklama);
SELECT * FROM urunler WHERE MATCH(ad, aciklama) AGAINST('laptop' IN BOOLEAN MODE);
-- PostgreSQL: pg_trgm eklentisi
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_urun_gin ON urunler USING GIN(ad gin_trgm_ops);
SELECT * FROM urunler WHERE ad ILIKE '%laptop%'; -- İndeks kullanır!
İndeks Stratejisi Özeti
| Durum | Öneri |
|---|---|
| WHERE ile sık sorgulanan sütun | B-Tree indeks ekle |
| JOIN sütunları | Her iki tabloya indeks |
| ORDER BY sütunu | İndekste aynı sıralama |
| Kısmi sonuç (WHERE + filtre) | Kısmi indeks |
| JSON alanı sorgulama | GIN indeks |
| Çok büyük tablo, zaman serisi | BRIN indeks |
| Sık güncellenen tablo | İndeks sayısını azalt |
İndeks optimizasyonu sürekli bir süreçtir. Uygulamanız büyüdükçe sorgu planlarını ve indeks kullanımını düzenli olarak izlemeyi unutmayın.
Buyukweb'in NVMe SSD tabanlı sunucuları veritabanı performansınız için idealdir. Hosting planlarımızı inceleyin.
Ilgili Buyukweb Hizmetleri:
- cPanel hosting ile MySQL yonetimi
- Windows hosting ile MSSQL destegi
- Tum hosting ve sunucu paketlerimiz
Veritabani Performans Optimizasyonu
Veritabani web uygulamalarinin kalbidir.
MySQL/MariaDB Tuning
innodb_buffer_pool_size'i RAM'in %60-70'ine ayarlayin. slow_query_log ile yavas sorgulari tespit edin. max_connections optimize edin.
Indeksleme
WHERE, JOIN, ORDER BY sutunlarina indeks ekleyin. EXPLAIN ile sorgu planlari analiz edin. Composite index kullanin.
Baglanti Havuzu
ProxySQL ile gelismis baglanti yonetimi. Connection pooling ile maliyet azaltma. Persistent connections kullanin.
Replikasyon
Master-Slave ile okuma yukunu dagitin. Galera Cluster ile multi-master yuksek erisilebilirlik. Semi-senkron replikasyon degerlendirin.
Yedekleme
mysqldump mantiksal, xtrabackup fiziksel yedek. Binary log ile point-in-time recovery. Incremental yedekleme ile tasarruf.
Sik Sorulan Sorular
MySQL mi PostgreSQL mi?
MySQL cogu web uygulamasi ile uyumlu. PostgreSQL gelismis veri tipleri ve JSON icin ideal. CMS'ler genelde MySQL kullanir.
Veritabanim buyudu ne yapmaliyim?
Gereksiz verileri temizleyin, tablo optimize edin, arsivleme yapin, partitioning kullanin.
Ne siklikla yedek almaliyim?
Kritik veritabanlari saatlik, standart siteler gunluk. Buyuk degisikliklerden once manuel yedek.
Sonuc
Veritabani optimizasyonu uygulama performansini dogrudan etkiler. Indeksleme, tuning ve yedekleme ile veri katmaninizi guclendirin.
Veritabani Boyut Yonetimi
Buyuk Tablolar icin Stratejiler
- Partitioning: Tarihe gore tablolari bolumlendirin. Sorgu performansi artar.
- Arsivleme: Eski verileri arsiv tablolarina tasiyin.
- Sikistirma: InnoDB sikistirmasi ile disk kullanimini %50-75 azaltin.
WordPress Veritabani Optimizasyonu
- wp_options autoload: Gereksiz autoload kayitlari temizleyin.
- Post revisions: wp-config.php'de WP_POST_REVISIONS sinirlayin.
- Transient veriler: Suresi dolmus verileri duzenli temizleyin.
- Spam yorumlar: Toplu silin.
Veritabani Guvenlik
- Varsayilan portu degistirin
- Uygulama bazli kullanici olusturun
- Minimum gerekli yetki verin
- SSL ile baglanti sifreleyin
- Duzenli guvenlik taramasi yapin
Etiketler:

