"Veritabanı yavaşladı" şikâyeti geldiğinde ilk refleks çoğu zaman donanım büyütmek olur. Oysa sahada karşılaştığımız yavaşlıkların büyük bölümü, birkaç temel noktanın gözden kaçmasından kaynaklanır. Sunucuya para harcamadan önce şu sırayla ilerleyin.
Önce ölçün, sonra müdahale edin
Tahminle optimizasyon yapılmaz. Yavaşlığın nerede olduğunu görmeden yapılan her değişiklik, işe yaradığını sanacağınız bir tesadüften ibarettir.
Lisansınız kapsıyorsa AWR ve ASH raporları en hızlı yoldur; yoksa v$session, v$sql ve v$session_wait üzerinden ilerleyebilirsiniz. Aradığınız şey iki sorunun cevabı: hangi SQL en çok kaynağı tüketiyor ve oturumlar neyi bekliyor.
-- en çok toplam süre harcayan sorgular
SELECT sql_id, executions, elapsed_time/1e6 AS toplam_sn,
elapsed_time/NULLIF(executions,0)/1e6 AS ortalama_sn, sql_text
FROM v$sql
ORDER BY elapsed_time DESC
FETCH FIRST 20 ROWS ONLY;
Ortalama süresi düşük ama çalışma sayısı çok yüksek bir sorgu, tek başına yavaş görünen bir sorgudan daha büyük sorun olabilir.
İstatistikler güncel mi
Oracle'ın maliyet tabanlı optimizasyonu, tablo ve indeks istatistiklerine göre plan seçer. İstatistikler eskiyse optimizer yanlış bilgiyle karar verir — ve genellikle kötü karar verir.
SELECT table_name, num_rows, last_analyzed
FROM user_tables
WHERE last_analyzed IS NULL
OR last_analyzed < SYSDATE - 7;
Toplu veri yüklemesi yaptığınız tablolarda istatistikleri iş bitiminde elle toplayın; otomatik toplama penceresini beklemek çoğu zaman geç kalır.
Yürütme planını gerçekten okuyun
Tahmini plan ile gerçekte çalışan plan farklı olabilir. Gerçek planı ve satır sayılarını görmek için:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(sql_id => '&sql_id',
format => 'ALLSTATS LAST'));
Planda dikkat edilecek en önemli şey, tahmini satır sayısı (E-Rows) ile gerçek satır sayısı (A-Rows) arasındaki fark. Aradaki uçurum büyükse optimizer yanılıyor demektir; kök neden genellikle bir önceki maddedir.
Bind değişkeni kullanın
Her seferinde farklı sabit değerle gönderilen sorgular, Oracle için her seferinde yeni bir sorgudur: shared pool şişer, hard parse maliyeti ve latch beklemeleri artar.
-- kötü: her müşteri için ayrı bir SQL
SELECT * FROM siparis WHERE musteri_id = 4711;
-- iyi: tek bir SQL, farklı bind değerleri
SELECT * FROM siparis WHERE musteri_id = :musteri_id;
Uygulama tarafında düzeltilemiyorsa cursor_sharing bir geçici çözüm olabilir, ama kalıcı çözüm uygulamada bind kullanmaktır.
İndeksi devre dışı bırakan yazımlardan kaçının
İndeks var ama kullanılmıyorsa, çoğu zaman sorgu yazımı suçludur.
Kolonu fonksiyonla sarmak: WHERE UPPER(ad) = 'AHMET' ifadesi ad üzerindeki normal indeksi kullanamaz. Ya sorguyu değiştirin ya da fonksiyon tabanlı indeks oluşturun.
Örtük tip dönüşümü: VARCHAR2 bir kolona sayı vermek (WHERE kod = 12345) Oracle'ı kolonu dönüştürmeye zorlar ve indeksi devre dışı bırakır. Değeri metin olarak gönderin: WHERE kod = '12345'.
Baştan joker: WHERE ad LIKE '%şirket' indeks taramasına uygun değildir; bu tür arama ihtiyaçları için metin arama indekslerini değerlendirin.
İndeks stratejisi: az ama doğru
Her yavaş sorguya bir indeks eklemek yaygın bir hatadır. Her indeks, okumayı hızlandırırken her INSERT/UPDATE/DELETE işlemine maliyet ekler.
Bileşik indekslerde kolon sırası belirleyicidir: eşitlikle filtrelenen ve seçiciliği yüksek kolonlar başa gelmelidir. Kullanılmayan indeksleri de tespit edip temizleyin — bunun için indeks kullanım izlemeden yararlanabilirsiniz.
Gereksiz veri çekmeyin
SELECT * alışkanlığı, yalnızca ağ ve bellek maliyeti değildir: gereken tüm kolonlar indekste bulunuyorsa Oracle tabloya hiç gitmeden sorguyu bitirebilir (covering index). Yıldız kullandığınızda bu olasılığı baştan yok edersiniz.
Aynı şekilde, uygulamada filtrelemek üzere tüm satırları çekmek yerine filtreyi veritabanına bırakın.
Satır satır işlemeyi bırakın
PL/SQL tarafında döngü içinde tek tek INSERT/UPDATE yapmak, büyük veri kümelerinde en sık karşılaştığımız performans katilidir. Toplu işlemler için BULK COLLECT ve FORALL kullanın; mümkünse işi tek bir küme işlemine (INSERT ... SELECT, MERGE) çevirin.
-- satır satır yerine tek küme işlemi
MERGE INTO hedef h
USING kaynak k ON (h.id = k.id)
WHEN MATCHED THEN UPDATE SET h.tutar = k.tutar
WHEN NOT MATCHED THEN INSERT (id, tutar) VALUES (k.id, k.tutar);
Sonra donanım
Bu sekiz maddeyi geçtikten sonra hâlâ darboğaz varsa, artık ölçülmüş bir gerekçeyle donanım veya yapılandırma konuşabilirsiniz — bellek, depolama gecikmesi, paralellik ayarları. Sıralamayı tersine çevirmek pahalı ve genellikle sonuçsuz olur.
Bu notu kendi ortamınıza uyarlarken sürüm farklarını gözden geçirin: sözdizimi ve görünüm adları sürümler arasında değişebilir, üretim ortamında değişiklik yapmadan önce test ortamında doğrulayın.