Veritabanı yavaşladığında ilk soru hep aynıdır: hangi sorgu? pg_stat_statements, bu sorunun tek doğru cevabıdır — çünkü tahmin değil, sunucunun açılışından beri biriken gerçek maliyeti gösterir. Ama uzantıyı CREATE EXTENSION ile açmak tek başına yetmez; en sık yapılan hata da bu.

Önce doğru kurulum

pg_stat_statements bir kütüphaneyi sunucu başlangıcında yüklemek zorundadır. shared_preload_libraries ayarı postgresql.conf içinde ayarlanır:

ini
PG 17
# postgresql.conf
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 10000
pg_stat_statements.track = top
Dikkat

shared_preload_libraries değişikliği sunucu restart'ı ister — reload yetmez, pg_reload_conf() de yetmez. Production'da bunu bakım penceresi olmadan yapma. Restart'ı unutursan uzantı yüklüymüş gibi görünür ama tablo boş kalır ve saatlerce "neden veri yok" diye ararsın.

Restart'tan sonra uzantıyı ilgili veritabanında etkinleştir:

sql
PG 17
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

En pahalı 10 sorgu

Sıralamayı toplam yürütme süresine göre yapmak, sistemin gerçek yükünü gösterir — tek tek yavaş olan değil, çok çalışıp toplamda pahalıya patlayan sorguları yakalarsın:

sql
PG 17
SELECT
  queryid,
  calls,
  round(total_exec_time::numeric, 1)      AS toplam_ms,
  round(mean_exec_time::numeric, 2)       AS ortalama_ms,
  round(100 * total_exec_time /
        sum(total_exec_time) OVER (), 1)  AS yuzde
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

yuzde sütunu kritik: tek bir sorgu toplam sürenin %40'ını yiyorsa, önce onunla ilgilenirsin. Ortalaması düşük ama calls değeri milyonlarca olan bir sorgu, "hızlı" görünse de sistemin sırtındaki asıl yüktür.

Dikkat

PostgreSQL 13 öncesinde sütun adı total_time / mean_time idi; 13 ile birlikte total_exec_time / mean_exec_time (planlama ve yürütme ayrıldı) oldu. Eski bir eğitimden kopyaladığın sorgu 17'de sütun bulunamadı hatası verirse sebebi budur.

Sayaçları sıfırlamak

Sunucu haftalardır ayaktaysa istatistikler eski. Bir değişikliğin (yeni indeks, parametre) etkisini ölçmek için sayaçları sıfırlayıp temiz bir pencere aç:

sql
PG 17
SELECT pg_stat_statements_reset();

Sonra yükü birkaç saat/gün topla, aynı sorguyu tekrar çalıştır. total_exec_time düştüyse değişiklik işe yaramış demektir. Bu, "herhalde hızlandı" demeden önce sahip olman gereken kanıttır.

I/O mı CPU mu?

Bir sorgu neden pahalı — diske mi gidiyor, CPU'da mı yanıyor? track_io_timing açıksa I/O'yu ayırabilirsin:

sql
PG 17
SELECT
  queryid,
  round(total_exec_time::numeric, 0)                 AS toplam_ms,
  round((shared_blk_read_time +
         shared_blk_write_time)::numeric, 0)         AS io_ms
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

io_ms, toplam_ms'e yakınsa sorun diskte (belki eksik indeks, belki yetersiz shared_buffers); çok düşükse darboğaz CPU'da (belki kötü plan, belki gereksiz sıralama).

Özet

pg_stat_statements üç adımda değer üretir: doğru preload + restart, toplam süreye göre sıralama, ve değişiklik öncesi/sonrası reset ile ölçüm. Buradan sonrası EXPLAIN (ANALYZE, BUFFERS) ile tek tek sorgulara dalmaktır — ama önce nereye dalacağını bu uzantı söyler.