MongoDB'de compound index, birden fazla alanı tek bir index içinde belirli bir sırayla tutar: db.siparisler.createIndex({ musteriId: 1, tarih: -1 }). Alan sırası belirleyicidir; index önce ilk alana, o alanın her değeri içinde ikinci alana göre sıralanır. Doğru sırayı seçmek için MongoDB belgeleri ESR kuralını önerir: önce eşitlik (Equality) alanları, sonra sıralama (Sort) alanları, en sonda aralık (Range) alanları.

Bu yazıda compound indexin nasıl çalıştığını, index öneki (prefix) kavramını, sıralama yönünün ne zaman önemli olduğunu, ESR kuralını ve bu kuraldan bilinçli olarak ne zaman sapılabileceğini örneklerle anlatıyoruz. Tek alanlı index oluşturma, index adları ve index silme konuları MongoDB index oluşturma yazısında yer alıyor.

Compound index nasıl saklanır?

Belgelere göre compound index, her belgedeki birden çok alan değerini toplayıp sıralar ve oluşturduğu B-tree, veriyi index tanımındaki alan sırasıyla saklar. { musteriId: 1, tarih: -1 } indexini düşünün: girdiler önce müşteri kimliğine göre artan sırada dizilir, aynı müşterinin siparişleri ise kendi içinde tarihe göre azalan sırada durur. Bir telefon rehberine benzetebilirsiniz: önce soyada, aynı soyadlılar arasında ada göre sıralı bir liste, soyadı bilinen birini bulmayı kolaylaştırır ama yalnızca adı bilinen birini bulmaya pek yardım etmez.

javascript
db.<koleksiyon>.createIndex( {
   <alan1>: <yön>,
   <alan2>: <yön>,
   ...
} )

// Örnek: müşterinin siparişleri, yeniden eskiye
db.siparisler.createIndex( { musteriId: 1, tarih: -1 } )

Tek bir compound index en fazla 32 alan içerebilir. Ayrıca bir compound index içinde yalnızca bir hashed alan bulunabilir ve dizi alanları için özel bir kural vardır: compound multikey bir indexte her belgede en fazla bir indexli alan dizi olabilir. İki alanı da dizi olan bir belge varsa index oluşturulamaz; index zaten varsa böyle bir belgenin eklenmesi başarısız olur.

Index öneki (prefix) kuralı

Index öneki (prefix) kuralı: urun, urun + depo, urun + depo + stok, urun + stok

Index öneki, index alanlarının baştan başlayan alt kümesidir. { urun: 1, depo: 1, stok: 1 } indexinin önekleri { urun: 1 } ve { urun: 1, depo: 1 } olur. Compound index, öneklerinden oluşan bütün alan kombinasyonlarını destekler: yalnızca urun, urun ile depo ve üç alanın birlikte kullanıldığı sorgular bu indexten yararlanır.

Sorgudaki alanlar{ urun: 1, depo: 1, stok: 1 } kullanılabilir mi?
urunEvet, önek
urun + depoEvet, önek
urun + depo + stokEvet, indexin tamamı
urun + stokEvet ama daha az verimli
depoHayır
stokHayır
depo + stokHayır

Tablodaki "daha az verimli" satırı önemli bir ayrıntıdır. urun ve stok alanlarını sorguladığınızda MongoDB indexi urun öneki üzerinden kullanabilir, ancak arada depo alanı olduğu için stok koşulunu index aralığını daraltmak için değil, taranan anahtarları süzmek için uygular. Belgelerdeki örnekte bir ürün değeriyle eşleşen 10000 belgeden yalnızca 100'ü stok koşulunu da sağlıyorsa sorgu yine 10000 anahtar inceler ve IXSCAN aşamasında bunları stoka göre süzerek 100 sonuç döndürür. Bu sorgu sık çalışıyorsa { urun: 1, stok: 1 } gibi daha uygun bir index düşünülmelidir.

Sıralama yönü ne zaman önemlidir?

Tek alanlı indexte yön önemsizdir, çünkü MongoDB indexi iki yönde de dolaşabilir. Compound indexte ise belgelerin ifadesiyle yön, yalnızca birden fazla alanı zıt yönlerde sıraladığınızda anlam kazanır. Bir compound index, sıralama düzeni index düzeniyle aynı olan ya da onun tam tersi olan sıralamaları destekler.

Bir oyun skor tablosu düşünelim: en yüksek puan en üstte, eşit puanlarda kullanıcı adı alfabetik sırada. Sorgu sort({ puan: -1, kullanici: 1 }) biçimindedir ve bunu destekleyen index aynı yönleri taşır:

javascript
db.skorlar.createIndex( { puan: -1, kullanici: 1 } )

// Index düzeniyle aynı: desteklenir
db.skorlar.find().sort( { puan: -1, kullanici: 1 } )

// Index düzeninin tam tersi: desteklenir (index ters dolaşılır)
db.skorlar.find().sort( { puan: 1, kullanici: -1 } )

// Ne aynı ne tersi: desteklenmez
db.skorlar.find().sort( { puan: 1, kullanici: 1 } )

// Alan sırası farklı: desteklenmez
db.skorlar.find().sort( { kullanici: 1, puan: -1 } )

Son iki sorguda MongoDB indexten sıralı sonuç alamaz ve belgeleri bellekte sıralamak zorunda kalır; explain çıktısında bu bir SORT aşaması olarak görünür. Belgelere göre MongoDB 6.0 ve sonrasında bir aşama 100 megabayttan fazla bellek isterse, sorgu { allowDiskUse: false } belirtmedikçe geçici dosyalar diske yazılır. Index kullanan sıralamalar ise genellikle bellekteki sıralamadan daha hızlıdır.

Sıralama ve önek

Sıralama anahtarları indexin tamamına ya da bir önekine karşılık geliyorsa index sıralamada kullanılabilir. { a: 1, b: 1, c: 1, d: 1 } indexi için sort({ a: 1 }), sort({ a: -1 }), sort({ a: 1, b: 1 }), sort({ a: -1, b: -1 }) ve sort({ a: 1, b: 1, c: 1 }) bellek içi sıralama gerektirmez. Filtredeki önek ile sıralamadaki önek farklı olabilir: find({ a: { $gt: 4 } }).sort({ a: 1, b: 1 }) da indexi kullanır.

Sıralama, önek olmayan bir alt kümeyle de yapılabilir; şartı, sıralama alanlarından önce gelen bütün index alanları için sorguda eşitlik koşulu bulunmasıdır. Aynı index için find({ a: 5 }).sort({ b: 1, c: 1 }) ve find({ b: 3, a: 4 }).sort({ c: 1 }) indexten sıralı sonuç alır. Buna karşılık find({ a: { $gt: 2 } }).sort({ c: 1 }) sorgusunda a için eşitlik değil aralık koşulu, b için hiç koşul olmadığından index sıralama için verimli kullanılamaz.

ESR kuralı: Equality, Sort, Range

ESR kuralı: Equality, Sort, Range: Equality (eşitlik), Sort (sıralama), Range (aralık)

Belgeler, çoğu durumda compound index alanlarını ESR kuralına göre dizmenin daha verimli bir index ortaya çıkardığını söyler. Kural üç tür koşulu ayırır ve onlara sabit bir sıra verir:

  • Equality (eşitlik): Tek bir değerle tam eşleşme, örneğin { durum: "kargoda" }. Eşitlik alanları her zaman en başta durmalıdır; böylece kalan index alanları sıralı kalır. Birden fazla eşitlik alanı kendi aralarında herhangi bir sırada olabilir, yeter ki hepsi sıralama ve aralık alanlarından önce gelsin. Eşleşme ne kadar seçiciyse sorgu o kadar verimli olur.
  • Sort (sıralama): Sonuçların düzenini belirleyen alanlar. Bellek içi sıralamayı önlemek için sıralama alanları aralık alanlarından önce gelmelidir ve yönleri sorgudaki sıralamayla uyumlu olmalıdır.
  • Range (aralık): $gt, $lt, $gte, $lte gibi tam eşleşme gerektirmeyen koşullar. Aralık koşulları index anahtarlarına gevşek bağlıdır; bu yüzden en sona konur ve sınırlarını mümkün olduğunca dar tutmak gerekir.

Kuralın gerekçesi index yapısından gelir. Eşitlik alanlarını sabitlediğinizde index içinde tek bir bitişik bölgeye inersiniz ve o bölgede sonraki alan zaten sıralıdır. Sıralama alanı hemen ardından gelirse MongoDB sonuçları o sırayla okuyabilir. Aralık alanı sıralama alanından önce gelseydi, aralığa giren her farklı değer için ayrı bir sıralı parça oluşur ve bunların birleştirilmesi için bellek içi sıralama gerekirdi.

Uygulamalı örnek

Bir sipariş ekranında "kargodaki ve tutarı 500 TL'nin altındaki siparişleri tarihe göre göster" sorgusu çalıştığını varsayalım. Sorgunun üç parçası ESR kuralına doğrudan karşılık gelir:

javascript
db.siparisler.find(
   { durum: "kargoda", tutar: { $lt: 500 } }
).sort( { tarih: 1 } )

// durum  : eşitlik (E)
// tarih  : sıralama (S)
// tutar  : aralık (R)

// ESR kuralına göre index:
db.siparisler.createIndex( { durum: 1, tarih: 1, tutar: 1 } )

Bu indexte MongoDB önce durum: "kargoda" bölgesine iner, o bölgedeki girdiler zaten tarihe göre sıralı olduğu için sonuçları sırayla okur ve tutar koşulunu indexteki değerlerle kontrol eder. { durum: 1, tutar: 1, tarih: 1 } gibi bir sıralama ise tutar aralığındaki farklı değerler yüzünden tarih sırasını bozar ve bellek içi sıralamaya yol açar.

ESR yerine ERS ne zaman?

Belgeler kuralı katı bir yasa olarak sunmaz. Eşitlik alanları her zaman başta kalır, ama sonrası ihtiyaca göre değişir: bellek içi sıralamadan kaçınmak kritikse sıralama alanını aralıktan önce koyarsınız (ESR); aralık koşulu çok seçiciyse, yani çok az belge döndürüyorsa aralık alanını sıralamanın önüne almak (ERS) taranacak ve sıralanacak belge sayısını azaltır ve küçük bir bellek içi sıralamayı kabul edilebilir kılar. Hangisinin daha iyi olduğunu kendi verinizde iki indexi hint() ile zorlayıp explain çıktılarını karşılaştırarak görebilirsiniz.

Operatörlerin ESR karşılığı

  • $ne ve $nin eşitsizlik bildirir; ESR açısından eşitlik değil aralık operatörüdür.
  • $regex bir aralık operatörü olarak değerlendirilir. Regex ile index ilişkisini regex sorguları yazısında anlatıyoruz.
  • $in tek başına kullanıldığında bir dizi eşitlik araması yapar ve eşitlik gibi davranır.
  • $in bir sort() ile birlikte kullanıldığında, dizide 201'den az eleman varsa değerler açılıp index sırasına göre bir SORT_MERGE aşamasında birleştirilir ve eşitlik gibi davranır. 201 veya daha fazla elemanda aralık operatörü gibi sıralanır ve sonraki index alanları sıralama için kullanılamaz. Belgeler bu 201 eşiğinin her sürümde aynı kalacağının garanti olmadığını da not eder.

Bu ayrıntının pratik karşılığı şudur: uygulamanız $in ile genellikle birkaç değer gönderiyorsa o alanı indexin başlarına, eşitlik alanlarının yanına koyabilirsiniz; büyük diziler gönderiyorsa alanı bir aralık alanının duracağı yere, yani sona yerleştirmek daha doğrudur. Operatörlerin genel kullanımını sorgu operatörleri yazısında bulabilirsiniz.

Seçicilik: hangi alan önce gelmeli?

Seçicilik, bir sorguya veya index anahtarına uyan belgelerin toplam belgelere oranıdır; az belge eşleşiyorsa seçicilik yüksektir. Belgelerdeki örnekte durum alanı belgelerin yüzde 99'unda aynı değeri taşıyorsa, bu değer için yapılan sorgu da index de düşük seçicidir; yüzde 1'lik diğer durumları arayan sorgu ise yüksek seçicidir. Eşitlik alanları arasında sıra serbest olsa da, sorgunun gerçekten daraltıcı olan koşullarını index kapsamına almak gerekir.

Belgeler bir başka örnekte, yalnızca durum alanında index varken { durum: { $gt: 5 }, urunTipi: "gida" } sorgusunun dört belge okumak zorunda kaldığını, urunTipi ve durum alanlarını içeren bir compound indexle ise aynı sorgunun daha verimli yanıtlanabildiğini, çünkü sorgunun zaten tek belge döndürdüğünü gösterir. Bu da ESR mantığının bir uygulamasıdır: eşitlik alanı (urunTipi) başa, aralık alanı (durum) sona.

Covered query: belgeye hiç dokunmadan sonuç

Compound index doğru kurulursa bazı sorgular yalnızca index üzerinden yanıtlanabilir. Belgelere göre bir sorgunun covered olması için filtredeki bütün alanların ve döndürülen bütün alanların aynı indexte bulunması, ayrıca filtrede null eşitliği olmaması gerekir. _id alanı indexte yoksa projeksiyonda açıkça _id: 0 yazmalısınız, aksi halde MongoDB belgeyi okumak zorunda kalır.

javascript
db.siparisler.createIndex( { durum: 1, musteriId: 1 } )

// Filtre ve dönen alanlar indexte, _id hariç tutuldu: covered query
db.siparisler.find(
   { durum: "teslim" },
   { musteriId: 1, _id: 0 }
)

Covered bir sorguda explain çıktısında IXSCAN aşaması bir FETCH aşamasının altında yer almaz. Multikey indexler dizi alanları üzerindeki sorguları cover edemez. Bu çıktıyı nasıl okuyacağınızı explain() rehberi anlatıyor.

İki indexi karşılaştırmak

Hangi alan sırasının daha iyi olduğundan emin değilseniz iki indexi de oluşturup her birini hint() ile zorlayarak totalKeysExamined ve nReturned değerlerini karşılaştırabilirsiniz. Belgelerdeki örnekte aynı sorgu için { year: 1, rated: 1 } indexi 235 sonuç için 246 anahtar incelerken, { rated: 1, year: 1 } indexi 235 anahtarla aynı sonucu verir; yani incelenen anahtar sayısı döndürülen belge sayısına eşit olan ikinci index daha verimlidir.

javascript
db.siparisler.createIndex( { tarih: 1, durum: 1 } )
db.siparisler.createIndex( { durum: 1, tarih: 1 } )

const sorgu = { durum: "kargoda", tarih: { $gte: ISODate("2026-10-01T00:00:00Z") } }

db.siparisler.find( sorgu ).hint( { tarih: 1, durum: 1 } ).explain( "executionStats" )
db.siparisler.find( sorgu ).hint( { durum: 1, tarih: 1 } ).explain( "executionStats" )

Karşılaştırma bittikten sonra kazanamayan indexi silmeyi unutmayın; kullanılmayan her index yazma maliyeti ve disk alanı demektir.

Sparse alanlarla compound index

Compound index içine sparse davranışlı index türleri girdiğinde hangi belgelerin indexleneceği değişir. Belgelere göre yalnızca artan ve azalan alanlardan oluşan sparse bir compound index, anahtarlardan en az birine değer içeren belgeleri indexler. Bileşimde geospatial bir alan varsa belge yalnızca geospatial alanlardan birinde değer içerdiğinde, text alanı varsa yalnızca text alanlarından biriyle eşleştiğinde indexlenir. Sparse ve partial seçeneklerini index türleri yazısında ayrıntılı ele alıyoruz.

Sık yapılan hatalar

Sık yapılan hatalar: Alanları sorgudaki yazılış sırasına göre dizmek, Aralık alanını başa koymak, Sıralama yönlerini karıştırmak, $ne ve $nin'i eşitlik sanmak
  • Alanları sorgudaki yazılış sırasına göre dizmek: Filtredeki alan sırası önemli değildir; index sırasını ESR kuralı belirler.
  • Aralık alanını başa koymak: { tarih: 1, durum: 1 } gibi bir sıra, eşitlik koşulunun indexi daraltmasını engeller.
  • Sıralama yönlerini karıştırmak: { puan: -1, kullanici: 1 } indexi { puan: 1, kullanici: 1 } sıralamasını desteklemez.
  • $ne ve $nin'i eşitlik sanmak: Bunlar aralık operatörüdür ve sona yerleştirilmelidir.
  • Önekini kapsayan gereksiz tek alanlı indexleri tutmak: Kısıt yoksa önek indexi silinebilir.
  • Covered query beklerken _id'yi dışlamamak: Projeksiyona _id: 0 eklenmezse belge okunur.

Sık Sorulan Sorular

Compound index tek alanlı indexlerin yerini tutar mı?

Önekleri için evet. { a: 1, b: 1 } indexi a üzerindeki sorguları da destekler. Ancak b tek başına sorgulanıyorsa bu index yardımcı olmaz; o sorgu için ayrı bir index gerekir.

Filtredeki alanların yazılış sırası indexi etkiler mi?

Hayır. Belgelere göre MongoDB'nin index stratejisi eşitlik alanlarını belirli bir sırada yazmanızı gerektirmez. Önemli olan index tanımındaki alan sırası ve sıralama alanından önce gelen alanlar için eşitlik koşulu bulunmasıdır.

ESR kuralı her zaman en iyi sonucu verir mi?

Çoğu durumda iyi bir başlangıçtır ama kesin değildir. Aralık koşulu çok seçiciyse ERS sırası daha az belge tarar. Kararı kendi verinizde explain ile ölçerek verin.

Bir compound index en fazla kaç alan içerebilir?

Belgelere göre 32 alan. Pratikte bu sınıra yaklaşmak genellikle index tasarımının gözden geçirilmesi gerektiğini gösterir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: MongoDB öğrenme rehberi · MongoDB index oluşturma · explain() ile sorgu planı · sort, limit ve sayfalama · Unique, partial ve sparse index · Sorgu operatörleri