# JSONB kolonundaki alanlarda filtrelemek için GIN index mi yoksa generated column mı tercih etmeliyim?

> Sabit anahtar başına bir expression index açın ve planın Index Scan'e düştüğünü doğrulayın; join'lerde de kullanıyorsanız generated column, dinamikte GIN.

- Soruldu: 2026-10-02
- Yanıtlandı: 2026-10-06
- Soran: Ekin
- Etiketler: postgresql, veritabani
- Kaynak: https://muhammetsafak.com/tr/sor-bakalim/jsonb-kolonundaki-alanlarda-filtrelemek-gin-index-generated-column/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** PostgreSQL'de bir tabloda `metadata` adlı bir JSONB kolonu tutuyorum ve sorgularımın çoğu bu blob içindeki birkaç sabit anahtara göre filtreleme yapıyor, örneğin `WHERE metadata->>'status' = 'active'` ve `WHERE metadata->>'region' = 'eu'`.

Tablo büyüdükçe bu sorgular giderek yavaşlıyor. Bu alanlarda filtrelemeyi hızlandırmak için tüm JSONB'ye bir GIN index mi koymalıyım, yoksa bu anahtarları generated column'a çıkarıp mı index'lemeliyim? Hangisi daha doğru?


Kısa cevap: Birkaç bilinen anahtara göre filtreliyorsanız cevap GIN değil.

## Kısa cevap

Sabit anahtarlar için en hafif çözüm bir expression index'tir; değeri gerçek kolon olarak da istiyorsanız generated column. GIN'i dinamik anahtar ve containment sorguları için saklayın.

## Neden

1. **İki farklı sorgu şekli, iki farklı araç.** GIN, `@>` containment ve `?` existence gibi operatörlerde ve önceden bilmediğiniz/çok sayıda anahtarda parlar. Generated column ve expression index ise bilinen, sabit birkaç anahtarda equality ve range için doğru araçtır. Sizin durumunuz ikincisi.

2. **Sizin durumunuz "birkaç sabit anahtar" → GIN overkill.** GIN index büyüktür, yazma sırasında güncellemesi pahalıdır ve range/ordering'e (`BETWEEN`, `ORDER BY`) yaramaz. Sadece iki anahtar için tüm blob'u index'lemek, ihtiyacınızdan büyük ve yavaş bir yapı kurmaktır.

## Ne yapmalı

1. **En hafif çözüm: expression index.** Değeri ifade üzerinden doğrudan index'lersiniz — ne şema değişikliği ne de ekstra kolon gerekir. Sorgunuz index'teki ifadeyle birebir aynı ifadeyi kullandığı sürece planner onu kullanır.

   ```sql
   CREATE INDEX idx_meta_status ON items ((metadata->>'status'));
   CREATE INDEX idx_meta_region ON items ((metadata->>'region'));
   -- Sorgu ifadeyle birebir eşleşmeli:
   SELECT * FROM items WHERE metadata->>'status' = 'active';
   ```

2. **Generated column ne zaman: değeri gerçek kolon olarak da istiyorsanız.** STORED bir generated column + b-tree index; planner istatistikleri (n_distinct, histogram) daha sağlıklı olur, sorgu daha okunur ve join'lerde kolona doğrudan başvurabilirsiniz. Bedeli: her anahtar için bir şema değişikliği ve storage tekrarı.

   ```sql
   ALTER TABLE items
     ADD COLUMN status text GENERATED ALWAYS AS (metadata->>'status') STORED;
   CREATE INDEX ON items (status);
   ```

3. **Cast tuzağına dikkat.** `->>'` her zaman `text` döner. Sayısal bir alanda range yapacaksanız (`(metadata->>'age')::int > 18`), cast'i hem index tanımında hem sorguda birebir aynı yazmalısınız — yoksa index kullanılmaz. NULL'lara ve boş değerlere de dikkat edin.

4. **GIN'i saklı tutun, silmeyin.** Anahtarlar dinamikse, [kullanıcı serbest metadata giriyorsa](/tr/sor-bakalim/esnek-urun-nitelikleri-icin-nosql-mu-postgresql-mi/) ya da `metadata @> '{"tags":["x"]}'` gibi containment yapıyorsanız GIN doğru araçtır — o zaman da yalnızca containment yapıyorsanız `jsonb_path_ops` varyantı daha küçük ve hızlıdır.

**Sonuç:** Ben olsam iki sabit anahtar için iki expression index açar, `EXPLAIN (ANALYZE, BUFFERS)` ile planın Seq Scan yerine Index Scan'e düştüğünü doğrulardım. Bu değerleri raporlarda ya da join'lerde de sık kullanıyorsam generated column'a terfi ettirirdim. GIN'i ancak anahtarlar öngörülemez hale gelirse ya da containment sorgularına geçersem devreye alırdım. Sabit anahtarda GIN, çekiçle ceviz kırmaktır.

## İlgili Yazılar

- [Heap erişimini önlemek ve index-only scan almak için covering index'e (INCLUDE) ne zaman geçmeliyim?](https://muhammetsafak.com/tr/sor-bakalim/index-only-scan-covering-indexe-ne-zaman-gecmeliyim/) — Sor Bakalım
- [Sadece 'pending' satırları taranan bir kuyruk tablosunda partial index kullanmalı mıyım?](https://muhammetsafak.com/tr/sor-bakalim/sadece-pending-satirlari-taranan-bir-kuyruk-tablosunda-partial-index-kullanmali-miyim/) — Sor Bakalım
- [WAL arşivleme ve PITR ile felaketten saniyeler öncesine nasıl dönerim?](https://muhammetsafak.com/tr/sor-bakalim/veritabani-pitr-ve-wal-arsivleme-ile-felaket-oncesine-donmek/) — Sor Bakalım
