Veritabanı optimizasyonu: Performans için sorgu iyileştirme ve indeksleme

Yavaş veritabanı, yavaş uygulama demektir. PostgreSQL, MySQL için performans iyileştirme teknikleri ve indeksleme stratejileri.

2026-01-2815 dk okuma
Veritabanı optimizasyonu: Performans için sorgu iyileştirme ve indeksleme

Veritabanı Optimizasyonu: Performans için Sorgu İyileştirme ve İndeksleme

Yüksek performanslı bir web uygulamasının temelinde, iyi optimize edilmiş bir veritabanı yatar. Yavaş veritabanı sorguları, kullanıcı deneyimini ciddi şekilde olumsuz etkiler ve sunucu kaynaklarının verimsiz kullanılmasına yol açar. Bu yazıda, PostgreSQL ve MySQL veritabanlarında sorgu optimizasyonu, indeksleme stratejileri ve caching tekniklerini detaylıca ele alacağız.

Veritabanı Performansı Neden Yavaşlar?

Veritabanı performans düşüklüğünün başlıca nedenleri şunlardır:

  • Eksik veya yanlış indeksler: Sorgular tam tablo taraması (full table scan) yapmak zorunda kalır.
  • Verimsiz sorgular: gereksiz JOIN'ler, alt sorgular veya besar veri kümeleri üzerinde çalışır.
  • Yetersiz kaynaklar: CPU, RAM veya disk I/O darboğazları oluşur.
  • Kötü veri modelleme: Normalizasyon eksikliği veya aşırı normalizasyon.
  • Caching eksikliği: Sık erişilen veriler her seferinde diskten okunur.

Sorgu Optimizasyon Teknikleri

EXPLAIN ve ANALYZE ile Sorgu Analizi

Herhangi bir sorguyu optimize etmeden önce, veritabanının sorguyu nasıl yürüttüğünü anlamanız gerekir. PostgreSQL'de EXPLAIN ANALYZE, MySQL'de EXPLAIN komutu kullanılır.

```sql -- PostgreSQL EXPLAIN ANALYZE SELECT u.name, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.created_at > '2025-01-01' GROUP BY u.name HAVING COUNT(o.id) > 5; ```

Bu komut size sorgunun执行 planını, satır sayılarını ve tahmini maliyeti gösterir. Seq Scan (sıralı tarama) görmek, genellikle indeks eksikliğine işaret eder.

Sorgu Yeniden Yazma

Birçok durumda, aynı sonucu daha verimli bir sorgu ile elde edebilirsiniz:

Kötü örnek - Alt sorgu kullanımı: ```sql SELECT * FROM products WHERE category_id IN ( SELECT id FROM categories WHERE active = true ) ```

İyi örnek - JOIN kullanımı: ```sql SELECT p.* FROM products p INNER JOIN categories c ON p.category_id = c.id WHERE c.active = true ```

Kötü örnek - SELECT * kullanımı: ```sql SELECT * FROM orders WHERE user_id = 123 ```

İyi örnek - Gerekli sütunları seçin: ```sql SELECT id, total, status, created_at FROM orders WHERE user_id = 123 ```

LIMIT Kullanımı

Büyük tablolardan veri çekerken her zaman LIMIT kullanın. Özellikle dashboard veya yönetim paneli gibi sayfalarda, tüm kayıtları çekmek gereksizdir.

```sql -- Son 50 siparişi getir SELECT * FROM orders ORDER BY created_at DESC LIMIT 50; ```

Batch İşlemler

Tek tek INSERT veya UPDATE işlemleri yerine, toplu işlem (batch) kullanın. Her bir veritabanı bağlantısı bir maliyet taşır.

```sql -- Tek tek yerine toplu ekleme INSERT INTO logs (user_id, action, created_at) VALUES (1, 'login', NOW()), (2, 'logout', NOW()), (3, 'purchase', NOW()); ```

İndeksleme Stratejileri

İndeks Nedir ve Ne Zaman Kullanılır?

İndeks, veritabanındaki verilere hızlı erişim sağlayan bir veri yapısıdır. Bir indeks, kitaptaki dizin gibidir - belirli bir terimi bulmak için tüm kitabı karıştırmanıza gerek kalmaz.

İndeks kullanmanız gereken durumlar:

  • WHERE cümlecğinde sıkça kullanılan sütunlar
  • JOIN işlemlerinde kullanılanforeign key sütunları
  • ORDER BY veya GROUP BY ile sıralanan sütunlar
  • Yüksek cardinality'ye (birçok farklı değer) sahip sütunlar

İndeks kullanmamanız gereken durumlar:

  • Çok küçük tablolar (bin satırın altı)
  • Sık güncellenen sütunlar (indeks güncelleme maliyeti)
  • Düşük cardinality'ye sahip sütunlar (true/false gibi)
  • Sık silinen tablolar

İndeks Türleri

B-Tree İndeks (Varsayılan): En yaygın indeks türü. Eşitlik ve aralık sorguları için idealdir.

```sql CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_orders_user_date ON orders(user_id, created_at); ```

Partial İndeks: Belirli bir koşulu sağlayan satırları indeksler. Büyük tablolarda alan tasarrufu sağlar.

```sql CREATE INDEX idx_active_users ON users(email) WHERE status = 'active'; ```

Covering İndeks (INCLUDE): Sorgu için gerekli tüm sütunları indeks içinde barındırır. Tabloya erişim gerektirmez.

```sql CREATE INDEX idx_orders_covering ON orders(user_id) INCLUDE (total, status, created_at); ```

Composite İndeks (Birleşik İndeks): Birden fazla sütunu birlikte indeksler. Sütun sırası kritiktir - en_selective (en ayırt edici) sütun önce gelmelidir.

```sql -- İyi: user_id daha selective CREATE INDEX idx_orders_user_status ON orders(user_id, status);

-- Kötü: status önce gelirse daha az etkili olur CREATE INDEX idx_orders_status_user ON orders(status, user_id); ```

İndeks Kullanımını Doğrulama

İndeksinizin kullanılıp kullanılmadığını kontrol edin:

```sql -- PostgreSQL: İndeks kullanım istatistikleri SELECT indexrelname, idx_scan FROM pg_stat_user_indexes WHERE schemaname = 'public' ORDER BY idx_scan DESC;

-- MySQL: İndeks kullanımı SHOW INDEX FROM orders; ```

Caching Stratejileri

Uygulama Seviyesinde Caching

Sık erişilen verileri uygulama belleğinde saklamak, veritabanı yükünü önemli ölçüde azaltır.

```typescript // Redis ile caching örneği import Redis from 'ioredis'

const redis = new Redis()

async function getUserById(id: number) { const cacheKey = user:${id}

// Önce cache'den kontrol et const cached = await redis.get(cacheKey) if (cached) { return JSON.parse(cached) }

// Cache'de yoksa veritabanından çek const user = await db.query('SELECT * FROM users WHERE id = $1', [id])

// 5 dakika cache'de sakla await redis.setex(cacheKey, 300, JSON.stringify(user))

return user } ```

Cache Invalidation

Cache invalidation, en zor bilgisayar bilimi problemlerinden biridir. Veri güncellendiğinde cache'in nasıl temizleneceğini doğru belirlemek gerekir.

  • TTL (Time To Live): Her cache kaydına bir süre sınırı koyun.
  • Write-through: Veri yazıldığında cache'i de güncelleyin.
  • Write-behind: Veriyi önce cache'e yazın, sonra arka planda veritabanına同步 edin.

N+1 Sorgu Problemi

ORM'lerin sıkça karşılaşılan bir sorunu olan N+1, bir listeleme sorgusu ardından her satır için ayrı sorgu çalıştırılmasıdır.

```typescript // N+1 problemi const orders = await Order.findAll() for (const order of orders) { order.user = await User.findById(order.userId) // Her satır için ayrı sorgu! }

// Çözüm: eager loading const orders = await Order.findAll({ include: [{ model: User, as: 'user' }] }) ```

Veritabanı Bağlantı Havuzu (Connection Pooling)

Her istek için yeni bir veritabanı bağlantısı açmak, büyük bir maliyettir. Bağlantı havuzu, hazır bağlantıları yeniden kullanarak bu maliyeti düşürür.

```typescript // PostgreSQL için pg-pool import { Pool } from 'pg'

const pool = new Pool({ max: 20, // Maksimum bağlantı sayısı idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, })

// Kullanım const result = await pool.query('SELECT * FROM users') ```

Monitoring ve Ölçüm

Performans optimizasyonunu sürekli yapmanın yolu, metrikleri izlemektir.

İzlemeniz gereken temel metrikler:

  • Sorgu süresi: Ortalama ve maximum sorgu çalışma süresi
  • Bağlantı kullanımı: Aktif ve bekleyen bağlantı sayısı
  • Cache hit oranı: Cache'den gelen istek oranı
  • Disk I/O: Okuma ve yazma hızları
  • İndeks kullanımı: Kullanılmayan indeksler

```sql -- PostgreSQL: Yavaş sorguları bulma SELECT query, mean_time, calls FROM pg_stat_statements ORDER BY mean_time DESC LIMIT 10; ```

Sonuç

Veritabanı optimizasyonu, sürekli bir süreçtir. İyi indeksleme, verimli sorgular ve akıllı caching stratejileri ile uygulamanızın performansını önemli ölçüde artırabilirsiniz. Her optimizasyon adımında ölçümler yapın ve sonuçları doğrulayın. Unutmayın, optimal performans bir hedef değil, sürekli bir yolculuktur.

İlgili Yazılar

Okuyucu Yorumlari

5.0 (0 yorum)

Mevcut Yorumlar

Henuz yorum yapilmamis. Ilk siz olun.

Yorum Birakin