# Heap erişimini önlemek ve index-only scan almak için covering index'e (INCLUDE) ne zaman geçmeliyim?

> Sıcak sorguda `EXPLAIN (ANALYZE, BUFFERS)` baskın maliyet olarak heap erişimini gösteriyorsa `INCLUDE` ekleyin, `Heap Fetches: 0` için autovacuum'u sıkın.

- Soruldu: 2026-09-02
- Yanıtlandı: 2026-09-08
- Soran: Pelin
- Etiketler: postgresql, veritabani, performans
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/index-only-scan-covering-indexe-ne-zaman-gecmeliyim/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** PostgreSQL'de sık çalışan bir lookup sorgum var: `WHERE customer_id = $1` ile filtreleyip `status` ve `total` kolonlarını okuyorum. `customer_id` üzerinde bir B-tree index'im zaten mevcut ve index bu satırları buluyor.

Sorun şu ki EXPLAIN çıktısında hâlâ bir heap erişimi görüyorum: index satırı bulduktan sonra `status` ve `total`'ı almak için tabloya (heap) gidiyor. Bu iki fazladan kolonu heap'e gitmeden almak için `INCLUDE` ile covering index'e ne zaman geçmeliyim, geçince gerçekten index-only scan alır mıyım?


Kısa cevap: Sorgu gerçekten sıcaksa (yüksek frekans, düşük gecikme beklentisi) ve heap erişimi ölçülmüş bir maliyetse geçin.

## Kısa cevap

Ama bir uyarı var: covering index tek başına index-only scan garantisi vermez — bu, tablonun visibility map durumuna bağlıdır.

## Neden

1. **INCLUDE, kolonları yalnızca leaf sayfalara koyar.** `INCLUDE (status, total)` bu kolonları B-tree'nin sıralama anahtarına eklemez; yani karşılaştırma maliyeti getirmezler ama sorguyu heap'e gitmeden karşılamak için orada dururlar. Amacınız tam olarak bu.

2. **Index-only scan visibility map'e bağlıdır.** Postgres heap'i yalnızca sayfa "all-visible" olarak işaretliyse atlar. Yazma yoğun bir tabloda bu harita vacuum'lar arasında bayatlar; o zaman covering index olsa bile EXPLAIN'de `Heap Fetches: N` görürsünüz. Yani asıl kaldıraç bazen index değil, autovacuum'u sıkılaştırmaktır.

   ```sql
   CREATE INDEX idx_orders_customer
     ON orders (customer_id)
     INCLUDE (status, total);

   EXPLAIN (ANALYZE, BUFFERS)
   SELECT status, total FROM orders WHERE customer_id = $1;
   -- Hedef: "Index Only Scan ... Heap Fetches: 0"
   ```

3. **Bu bir yazma/alan takasıdır.** Daha geniş leaf sayfaları = daha büyük index, daha fazla WAL, daha yavaş yazma, cache'te tutulacak daha çok veri. Bu maliyeti yalnızca gerçekten sıcak okumalar hak eder; her sorgu için covering index dökmeyin.

4. **INCLUDE kolonları yalnızca projeksiyon içindir.** Bunu unutmayın: `INCLUDE` ile eklenen kolonlar `WHERE` filtresinde ya da `ORDER BY`'da kullanılamaz, sadece `SELECT` çıktısını karşılamak için oradadır. Eğer o kolon üzerinde de filtreleyecekseniz onu anahtara (belki [bileşik index'e](/sor-bakalim/postgresql-composite-index-kolon-sirasi-nasil-secilir/)) koymanız gerekir; ihtiyacınız sadece "geri döndürmek" ise INCLUDE doğru yerdir.

## Ne yapmalı

1. **Kolonu anahtara eklemektense INCLUDE'u tercih edin.** `status`, `total`'ı index anahtarına koymak iç düğümleri şişirebilir ve ihtiyacınız olmayan bir sıralama dayatır. INCLUDE ağacı ince tutar, sadece leaf'i genişletir — sorguyu karşılamak için ihtiyacınız olan tam da bu.

2. **EXPLAIN (ANALYZE, BUFFERS) ile doğrulayın.** "Index Only Scan" ve "Heap Fetches: 0" görene kadar iş bitmiş sayılmaz. Heap fetch yüksek kalıyorsa çözüm daha fazla index değil, `VACUUM` (ya da autovacuum eşiklerini düşürmek) olabilir. İndex'in gerçekten kullanılıp kullanılmadığını `pg_stat_user_indexes` ile takip edin; kimsenin dokunmadığı bir covering index sadece yazma yükü demektir.

**Sonuç:** Ben olsam önce `EXPLAIN (ANALYZE, BUFFERS)` ile heap erişiminin gerçekten baskın maliyet olduğunu doğrular, sonra `customer_id` index'ini `INCLUDE (status, total)` ile yeniden oluşturur ve `Heap Fetches: 0` görene kadar autovacuum'u bu tablo için sıkılaştırırdım. Zaman içinde index'in şiştiğini görürseniz `REINDEX CONCURRENTLY` ile toparlayın. Kısacası: bu iki kolon gerçekten sıcak bir sorgunun read-only yükü ise covering index kazandırır; değilse ölçmeden index eklemek çoğu zaman sadece yazma yolunu yavaşlatır.

## İlgili Yazılar

- [Sadece 'pending' satırları taranan bir kuyruk tablosunda partial index kullanmalı mıyım?](https://www.muhammetsafak.com.tr/sor-bakalim/sadece-pending-satirlari-taranan-bir-kuyruk-tablosunda-partial-index-kullanmali-miyim/) — Sor Bakalım
- [PgBouncer'da Session/Transaction/Statement modlarından hangisini seçmeliyim?](https://www.muhammetsafak.com.tr/sor-bakalim/pgbouncer-session-transaction-statement-modu-ve-prepared-statement/) — Sor Bakalım
- [PostgreSQL'de composite index kolon sırasını nasıl seçerim?](https://www.muhammetsafak.com.tr/sor-bakalim/postgresql-composite-index-kolon-sirasi-nasil-secilir/) — Sor Bakalım
