15-bo‘lim

Indekslar

Indeks nima, bitta va ko'p maydonli indekslar, ESR qoidasi, yagona va qisman indekslar, TTL, matn indeksi hamda indeksning narxi.

🕑 18 daqiqa o‘qish 📄 1 079 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Indekssiz so'rov
  2. Indeks qo'yamiz
  3. Indeks turlari
  4. Ko'p maydonli indeks va ESR qoidasi
  5. Prefiks qoidasi
  6. Yagona indeks
  7. Qisman indeks
  8. TTL indeks
  9. Matn indeksi
  10. Indeksning narxi
  11. Xulosa

Indekssiz baza har so'rovda butun to'plamni o'qiydi. Yigirma hujjatda bu sezilmaydi, yigirma millionda esa dastur ishlamay qoladi.

JavaScript
const filiallar = ["Namangan", "Toshkent", "Samarqand"]
const hujjatlar = []
for (let i = 1; i <= 20000; i++) {
  hujjatlar.push({
    _id: i,
    mijoz_id: 1 + (i % 2000),
    filial: filiallar[i % 3],
    holat: (i % 10 === 0) ? "bekor" : "yakunlandi",
    summa: 10000 + (i % 500) * 100,
    sana: new Date(Date.UTC(2026, 0, 1 + (i % 90)))
  })
}
db.buyurtma.insertMany(hujjatlar)

Indekssiz so'rov #

20 000 hujjat bor. Bitta mijozning buyurtmalarini qidiramiz:

JavaScript
const e = db.buyurtma.find({ mijoz_id: 7 }).explain("executionStats")
e.queryPlanner.winningPlan.stage
e.executionStats.nReturned
e.executionStats.totalKeysExamined
e.executionStats.totalDocsExamined
Natija
COLLSCAN
10
0
20000

Uchta muhim son:

Ko'rsatkichQiymatMa'nosi
nReturned10Qaytgan hujjatlar
totalKeysExamined0Ko'rilgan indeks kalitlari
totalDocsExamined20000Ko'rilgan hujjatlar

10 ta hujjat uchun 20 000 tasi o'qildi. COLLSCAN - "collection scan", ya'ni butun to'plamni ko'rish.

Indeks qo'yamiz #

JavaScript
db.buyurtma.createIndex({ mijoz_id: 1 })
const e = db.buyurtma.find({ mijoz_id: 7 }).explain("executionStats")
e.queryPlanner.winningPlan.stage
e.executionStats.nReturned
e.executionStats.totalKeysExamined
e.executionStats.totalDocsExamined
Natija
mijoz_id_1

FETCH
10
10
10

totalDocsExamined 20 000 dan 10 ga tushdi. Bu ikki ming baravar kam ish.

Bir xil so'rov, ikki xil ish hajmi Indekssiz: COLLSCAN 20 000 hujjat o'qildi 10 tasi kerak edi 99.95% ish bekorga ketdi Indeks bilan: IXSCAN 10 kalit ko'rildi 10 hujjat o'qildi Faqat kerakligi o'qildi Eng muhim nisbat totalDocsExamined va nReturned bir-biriga yaqin bo'lsa - indeks yaxshi Farq katta bo'lsa - indeks yo'q yoki noto'g'ri tanlangan
Bu nisbat so'rovni baholashning eng tez usuli

Indeks turlari #

TurYozilishiQachon
Bitta maydon{ mijoz_id: 1 }Bitta shart bo'yicha qidiruv
Ko'p maydonli{ filial: 1, sana: -1 }Bir necha shart va saralash
Massiv (ko'p kalitli){ teglar: 1 }Massiv elementlari bo'yicha
Matn{ tavsif: "text" }So'z bo'yicha qidiruv
Geografik{ joy: "2dsphere" }Masofa bo'yicha
Xeshli{ mijoz_id: "hashed" }Bo'laklash uchun
JavaScript
db.buyurtma.createIndex({ filial: 1, sana: -1 })
db.buyurtma.createIndex({ holat: 1 })
db.buyurtma.getIndexes().map(i => i.name)
Natija
filial_1_sana_-1
holat_1
[ '_id_', 'filial_1_sana_-1', 'holat_1' ]

Ko'p maydonli indeks va ESR qoidasi #

Ko'p maydonli indeksda tartib hal qiluvchi. To'g'ri tartibni tanlash uchun ESR qoidasi bor:

HarfNimani anglatadiMisol
EEquality - tenglik shartifilial: "Namangan"
SSort - saralash maydonisort({ sana: -1 })
RRange - oraliq shartisumma: { $gt: 50000 }

Ya'ni indeks { tenglik, saralash, oraliq } tartibida tuzilishi kerak.

Rejadagi bosqichlarni ko'rish uchun kichik yordamchi yozamiz:

JavaScript
function bosqichlar(reja) {
  const r = []
  let n = reja
  while (n) { r.push(n.stage); n = n.inputStage }
  return r.join(" <- ")
}
db.buyurtma.createIndex({ filial: 1, sana: -1, summa: 1 })
const f = { filial: "Namangan", summa: { $gt: 50000 } }
const k1 = db.buyurtma.find(f).sort({ sana: -1 })
const yaxshi = k1.explain("executionStats")
bosqichlar(yaxshi.queryPlanner.winningPlan)
yaxshi.executionStats.totalDocsExamined
Natija
[Function: bosqichlar]
filial_1_sana_-1_summa_1



FETCH <- IXSCAN
1320

Endi xuddi shu indeksni noto'g'ri tartibda quramiz - oraliqni saralashdan oldin qo'yamiz:

JavaScript
function bosqichlar(reja) {
  const r = []
  let n = reja
  while (n) { r.push(n.stage); n = n.inputStage }
  return r.join(" <- ")
}
db.buyurtma.createIndex({ filial: 1, summa: 1, sana: -1 })
const f = { filial: "Namangan", summa: { $gt: 50000 } }
const k2 = db.buyurtma.find(f).sort({ sana: -1 })
const yomon = k2.explain("executionStats")
bosqichlar(yomon.queryPlanner.winningPlan)
yomon.executionStats.totalDocsExamined
Natija
[Function: bosqichlar]
filial_1_summa_1_sana_-1



FETCH <- SORT <- IXSCAN
1320

Ikkala holatda ham bir xil son hujjat o'qildi, lekin ikkinchi rejada qo'shimcha SORT bosqichi paydo bo'ldi. U 1320 ta hujjatni xotirada qayta saraladi - birinchi reja esa buni umuman qilmadi.

ESR qoidasi nima uchun ishlaydi

Indeks - tartiblangan ro'yxat. Uni telefon kitobiga o'xshating: familiya, keyin ism bo'yicha tartiblangan.

Tenglik sharti kitobning aniq bir joyiga olib boradi - "Qodirov" bo'limiga. Shundan keyin qolgan hamma narsa shu kichik bo'lim ichida.

Saralash keyingi o'rinda bo'lsa, o'sha bo'lim ichida yozuvlar allaqachon kerakli tartibda yotadi - qo'shimcha saralash kerak emas.

Oraliq eng oxirida bo'lishi kerak, chunki u indeksda ketma-ket bo'lak oladi va undan keyingi maydonlarning tartibini buzadi.

Agar oraliqni saralashdan oldin qo'ysangiz, baza indeksdan tartibni ola olmaydi va natijani xotirada qayta saralaydi. Bu SORT bosqichi rejada ko'rinadi va katta natijada 32 MB chegarasiga urilib, so'rov xato berishi mumkin.

Prefiks qoidasi #

{ filial: 1, sana: -1 } indeksi quyidagilarga yaraydi:

So'rovYaraydimi
{ filial: "Namangan" }Ha
{ filial: "Namangan", sana: ... }Ha
{ sana: ... }Yo'q

Sabab: indeks avval filial bo'yicha tartiblangan. filial noma'lum bo'lsa, sana qayerda turishini bilib bo'lmaydi.

JavaScript
db.buyurtma.createIndex({ filial: 1, sana: -1 })
const a = db.buyurtma.find({ filial: "Toshkent" }).explain()
a.queryPlanner.winningPlan.inputStage.stage
const b = db.buyurtma.find({ sana: new Date(Date.UTC(2026, 0, 5)) }).explain()
b.queryPlanner.winningPlan.stage
Natija
filial_1_sana_-1

IXSCAN

COLLSCAN

Ikkinchisi COLLSCAN - indeks ishlamadi.

Yagona indeks #

JavaScript
db.mijoz.createIndex({ email: 1 }, { unique: true })
db.mijoz.insertOne({ _id: 1, ism: "Husanboy", email: "[email protected]" })
db.mijoz.insertOne({ _id: 2, ism: "Malika", email: "[email protected]" })
Natija
email_1
{ acknowledged: true, insertedId: 1 }
MongoServerError: E11000 duplicate key error collection: dokon.mijoz index: email_1 dup key: { email: "[email protected]" }

Qisman indeks #

Faqat shartga mos hujjatlarni indekslaydi - joy tejaydi:

JavaScript
db.buyurtma.createIndex(
  { sana: -1 },
  { partialFilterExpression: { holat: "bekor" } }
)
const e = db.buyurtma.find({
  holat: "bekor",
  sana: { $gte: new Date(Date.UTC(2026, 2, 1)) }
}).explain("executionStats")
e.executionStats.nReturned
e.executionStats.totalDocsExamined
Natija
sana_-1

666
666

20 000 hujjatdan faqat 2 000 tasi bekor - shuning uchun indeks ham o'n baravar kichik.

Qisman indeks so'rovda ham shu shart bo'lsagina ishlaydi

partialFilterExpression: { holat: "bekor" } bilan qurilgan indeksdan foydalanish uchun so'rovda ham holat: "bekor" bo'lishi shart.

Natija
db.buyurtma.find({ sana: { $gte: ... } })

Bu so'rov indeksdan foydalanmaydi, chunki baza "indeksda hamma hujjat bormi?" degan savolga ishonchli javob bera olmaydi.

Bu yumshoq o'chirish naqshi bilan juda yaxshi ishlaydi:

Natija
db.mijoz.createIndex(
  { email: 1 },
  { unique: true,
    partialFilterExpression: { ochirilgan: { $exists: false } } }
)

Shunda "o'chirilgan" foydalanuvchi emailni band qilib turmaydi va o'sha email bilan yangi hisob ochish mumkin bo'ladi.

TTL indeks #

Hujjatlarni ma'lum vaqtdan keyin avtomatik o'chiradi:

JavaScript
db.sessiya.createIndex({ yaratilgan: 1 }, { expireAfterSeconds: 3600 })
const i = db.sessiya.getIndexes().find(x => x.name === "yaratilgan_1")
i.expireAfterSeconds
Natija
yaratilgan_1

3600
FoydalanishMisol
Sessiyalar1 soatdan keyin o'chsin
Kesh10 daqiqadan keyin
Loglar30 kundan keyin
Vaqtinchalik kodlar5 daqiqadan keyin
TTL aniq soniyada ishlamaydi

Fon jarayoni har 60 soniyada bir marta ishga tushadi va muddati o'tgan hujjatlarni o'chiradi.

Ya'ni hujjat muddati tugagandan keyin ham bir daqiqagacha bazada turishi mumkin. Yuk katta bo'lsa, bundan ham ko'proq.

Shuning uchun TTL ni xavfsizlik chorasi sifatida ishlatmang. "Token 5 daqiqadan keyin o'chadi" degan kafolat kerak bo'lsa, tokenning amal qilish vaqtini hujjatda saqlang va o'qiyotganda tekshiring. TTL esa eski yozuvlarni tozalash uchun qolsin.

Yana bir tafsilot: maydon Date turida bo'lishi shart. Matn yoki son bo'lsa, TTL shunchaki ishlamaydi - va hech qanday xato ham bermaydi.

Matn indeksi #

JavaScript
db.maqola.insertMany([
  { _id: 1, sarlavha: "MongoDB indekslari",
    matn: "Indeks qidiruvni tezlashtiradi" },
  { _id: 2, sarlavha: "Agregatsiya quvuri",
    matn: "Quvur bosqichlardan iborat" },
  { _id: 3, sarlavha: "Tranzaksiyalar",
    matn: "Indeks va tranzaksiya birga ishlaydi" }
])
db.maqola.createIndex({ sarlavha: "text", matn: "text" })
db.maqola.find({ $text: { $search: "indeks" } }, { _id: 1, sarlavha: 1 })
Natija
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3 } }
sarlavha_text_matn_text
[
  { _id: 1, sarlavha: 'MongoDB indekslari' },
  { _id: 3, sarlavha: 'Tranzaksiyalar' }
]

Matn indeksi so'zlarni ajratadi va ularni alohida indekslaydi. Bitta to'plamda faqat bitta matn indeksi bo'lishi mumkin.

Indeksning narxi #

Indeks bepul emas:

NarxIzoh
DiskHar indeks alohida joy egallaydi
Yozuv tezligiHar insert va update da indeks ham yangilanadi
XotiraIndeks keshda turishi kerak, aks holda foydasi kam
Reja tanlashKo'p indeks - rejalashtiruvchiga ko'p ish
JavaScript
db.buyurtma.createIndex({ filial: 1 })
db.buyurtma.createIndex({ holat: 1 })
db.buyurtma.createIndex({ summa: 1 })
db.buyurtma.getIndexes().length
db.buyurtma.dropIndex("summa_1").ok
db.buyurtma.getIndexes().length
Natija
filial_1
holat_1
summa_1
4
1
3
Ishlatilmayotgan indekslarni toping va o'chiring

Loyihalar odatda indeksni qo'shadi, lekin hech qachon o'chirmaydi. Bir yildan keyin to'plamda 15 ta indeks bo'ladi, ulardan 4 tasi ishlatiladi.

MongoDB har indeksning ishlatilish statistikasini yuritadi:

Natija
db.buyurtma.aggregate([{ $indexStats: {} }])

Natijada har indeks uchun accesses.ops - u necha marta ishlatilgani ko'rinadi. Nol bo'lsa - nomzod.

Ikki ehtiyot shart:

  1. Server qayta ishga tushganda hisoblagich nolga qaytadi. Shuning uchun kamida bir hafta kuting.
  2. Oyda bir marta ishlaydigan hisobot indeksi ham nol ko'rinishi mumkin.

O'chirishdan oldin hidden: true bilan indeksni yashiring: u saqlanib qoladi, lekin rejalashtiruvchi uni ko'rmaydi. Bir hafta muammo bo'lmasa - o'chiring. Muammo chiqsa - bitta buyruq bilan qaytarasiz.

Amaliy topshiriq
  1. Indekssiz so'rovda totalDocsExamined ni o'lchang.
  2. Indeks qo'shib, xuddi shu o'lchovni takrorlang.
  3. nReturned va totalDocsExamined nisbatini tushuntiring.
  4. Ko'p maydonli indeks yarating.
  5. ESR qoidasini o'z so'zingiz bilan yozing.
  6. Prefiks qoidasini misolda ko'rsating.
  7. Yagona indeks qo'yib, takroriy qiymat kiriting.
  8. Qisman indeks yarating va uning shartini tushuntiring.
  9. TTL indeks qo'ying va uning kechikishi haqida yozing.
  10. Indeksni o'chirishdan oldin nima qilish kerakligini yozing.

Xulosa #

  • Indekssiz so'rov butun to'plamni o'qiydi - bu COLLSCAN.
  • Indeks bilan faqat kerakli hujjatlar o'qiladi - bu IXSCAN.
  • Eng muhim o'lchov - totalDocsExamined va nReturned nisbati.
  • Ko'p maydonli indeksda tartib hal qiluvchi: { tenglik, saralash, oraliq } - ESR.
  • Prefiks qoidasi: indeks faqat o'zining boshidan boshlangan shartlarga yaraydi.
  • Yagona indeks takrorlanishdan himoya qiladi va E11000 beradi.
  • Qisman indeks so'rovda ham shu shart bo'lsagina ishlaydi.
  • TTL indeks hujjatlarni avtomatik o'chiradi, lekin 60 soniyagacha kechikadi.
  • TTL ni xavfsizlik chorasi sifatida ishlatmang.
  • Indeks bepul emas - $indexStats bilan ishlatilmayotganlarini toping va avval yashiring.

Keyingi bo'limda explain natijasini to'liq o'qishni o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.