15-bo‘lim
Indekslar
Indeks nima, bitta va ko'p maydonli indekslar, ESR qoidasi, yagona va qisman indekslar, TTL, matn indeksi hamda indeksning narxi.
Ushbu bo‘lim mundarijasi
Indekssiz baza har so'rovda butun to'plamni o'qiydi. Yigirma hujjatda bu sezilmaydi, yigirma millionda esa dastur ishlamay qoladi.
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:
const e = db.buyurtma.find({ mijoz_id: 7 }).explain("executionStats")
e.queryPlanner.winningPlan.stage
e.executionStats.nReturned
e.executionStats.totalKeysExamined
e.executionStats.totalDocsExamined
COLLSCAN
10
0
20000
Uchta muhim son:
| Ko'rsatkich | Qiymat | Ma'nosi |
|---|---|---|
nReturned | 10 | Qaytgan hujjatlar |
totalKeysExamined | 0 | Ko'rilgan indeks kalitlari |
totalDocsExamined | 20000 | Ko'rilgan hujjatlar |
10 ta hujjat uchun 20 000 tasi o'qildi. COLLSCAN -
"collection scan", ya'ni butun to'plamni ko'rish.
Indeks qo'yamiz #
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
mijoz_id_1
FETCH
10
10
10
totalDocsExamined 20 000 dan 10 ga tushdi. Bu ikki
ming baravar kam ish.
Indeks turlari #
| Tur | Yozilishi | Qachon |
|---|---|---|
| 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 |
db.buyurtma.createIndex({ filial: 1, sana: -1 })
db.buyurtma.createIndex({ holat: 1 })
db.buyurtma.getIndexes().map(i => i.name)
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:
| Harf | Nimani anglatadi | Misol |
|---|---|---|
| E | Equality - tenglik sharti | filial: "Namangan" |
| S | Sort - saralash maydoni | sort({ sana: -1 }) |
| R | Range - oraliq sharti | summa: { $gt: 50000 } |
Ya'ni indeks { tenglik, saralash, oraliq } tartibida
tuzilishi kerak.
Rejadagi bosqichlarni ko'rish uchun kichik yordamchi yozamiz:
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
[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:
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
[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.
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'rov | Yaraydimi |
|---|---|
{ 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.
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
filial_1_sana_-1
IXSCAN
COLLSCAN
Ikkinchisi COLLSCAN - indeks ishlamadi.
Yagona indeks #
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]" })
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:
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
sana_-1
666
666
20 000 hujjatdan faqat 2 000 tasi bekor - shuning uchun
indeks ham o'n baravar kichik.
partialFilterExpression: { holat: "bekor" } bilan
qurilgan indeksdan foydalanish uchun so'rovda ham
holat: "bekor" bo'lishi shart.
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:
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:
db.sessiya.createIndex({ yaratilgan: 1 }, { expireAfterSeconds: 3600 })
const i = db.sessiya.getIndexes().find(x => x.name === "yaratilgan_1")
i.expireAfterSeconds
yaratilgan_1
3600
| Foydalanish | Misol |
|---|---|
| Sessiyalar | 1 soatdan keyin o'chsin |
| Kesh | 10 daqiqadan keyin |
| Loglar | 30 kundan keyin |
| Vaqtinchalik kodlar | 5 daqiqadan keyin |
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 #
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 })
{ 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:
| Narx | Izoh |
|---|---|
| Disk | Har indeks alohida joy egallaydi |
| Yozuv tezligi | Har insert va update da indeks ham yangilanadi |
| Xotira | Indeks keshda turishi kerak, aks holda foydasi kam |
| Reja tanlash | Ko'p indeks - rejalashtiruvchiga ko'p ish |
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
filial_1
holat_1
summa_1
4
1
3
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:
db.buyurtma.aggregate([{ $indexStats: {} }])
Natijada har indeks uchun accesses.ops - u necha marta
ishlatilgani ko'rinadi. Nol bo'lsa - nomzod.
Ikki ehtiyot shart:
- Server qayta ishga tushganda hisoblagich nolga qaytadi. Shuning uchun kamida bir hafta kuting.
- 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.
- Indekssiz so'rovda
totalDocsExaminedni o'lchang. - Indeks qo'shib, xuddi shu o'lchovni takrorlang.
nReturnedvatotalDocsExaminednisbatini tushuntiring.- Ko'p maydonli indeks yarating.
- ESR qoidasini o'z so'zingiz bilan yozing.
- Prefiks qoidasini misolda ko'rsating.
- Yagona indeks qo'yib, takroriy qiymat kiriting.
- Qisman indeks yarating va uning shartini tushuntiring.
- TTL indeks qo'ying va uning kechikishi haqida yozing.
- 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 -
totalDocsExaminedvanReturnednisbati. - 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
E11000beradi. - 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 -
$indexStatsbilan ishlatilmayotganlarini toping va avval yashiring.
Keyingi bo'limda explain natijasini to'liq o'qishni
o'rganamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.