9-bo‘lim
Kursor, saralash va sahifalash
Kursor qanday ishlaydi, sort va limit, skip nima uchun sekin, kursor bo'yicha sahifalash hamda countDocuments va estimatedDocumentCount farqi.
Ushbu bo‘lim mundarijasi
find() hujjatlarni qaytarmaydi - u kursor qaytaradi.
Bu farqni tushunish katta to'plamlar bilan ishlashda hal
qiluvchi ahamiyatga ega.
db.kitob.insertMany([
{ _id: 1, nom: "Otkan kunlar", yil: 1926, narx: 85000, janr: "tarixiy" },
{ _id: 2, nom: "Mehrobdan chayon", yil: 1929, narx: 72000,
janr: "tarixiy" },
{ _id: 3, nom: "Sarob", yil: 1935, narx: 59000, janr: "ijtimoiy" },
{ _id: 4, nom: "Kecha va kunduz", yil: 1936, narx: 68000,
janr: "ijtimoiy" },
{ _id: 5, nom: "Navoiy", yil: 1944, narx: 95000, janr: "tarixiy" },
{ _id: 6, nom: "Shum bola", yil: 1936, narx: 47000, janr: "bolalar" },
{ _id: 7, nom: "Ufq", yil: 1964, narx: 64000, janr: "zamonaviy" },
{ _id: 8, nom: "Yulduzli tunlar", yil: 1978, narx: 78000, janr: "tarixiy" }
])
Kursor nima #
Kursor - serverdagi ochiq natija oqimi. U hamma hujjatni bir vaqtda yubormaydi, balki to'plam-to'plam (odatda 101 ta, keyin ~4 MB lik bloklarda) uzatadi.
sort #
db.kitob.find({}, { nom: 1, narx: 1, _id: 0 }).sort({ narx: -1 }).limit(3)
[
{ nom: 'Navoiy', narx: 95000 },
{ nom: 'Otkan kunlar', narx: 85000 },
{ nom: 'Yulduzli tunlar', narx: 78000 }
]
| Qiymat | Tartib |
|---|---|
1 | O'sish bo'yicha |
-1 | Kamayish bo'yicha |
Bir nechta maydon bo'yicha saralash - tartib muhim:
const p = { _id: 0, nom: 1, janr: 1, yil: 1 }
db.kitob.find({}, p).sort({ janr: 1, yil: -1 })
[
{ nom: 'Shum bola', yil: 1936, janr: 'bolalar' },
{ nom: 'Kecha va kunduz', yil: 1936, janr: 'ijtimoiy' },
{ nom: 'Sarob', yil: 1935, janr: 'ijtimoiy' },
{ nom: 'Yulduzli tunlar', yil: 1978, janr: 'tarixiy' },
{ nom: 'Navoiy', yil: 1944, janr: 'tarixiy' },
{ nom: 'Mehrobdan chayon', yil: 1929, janr: 'tarixiy' },
{ nom: 'Otkan kunlar', yil: 1926, janr: 'tarixiy' },
{ nom: 'Ufq', yil: 1964, janr: 'zamonaviy' }
]
Avval janr bo'yicha guruhlandi, har guruh ichida esa
yil kamayish tartibida.
Ikkita hujjatning saralash maydoni teng bo'lsa, ular orasidagi tartib kafolatlanmagan. Xuddi shu so'rovni ikki marta bajarib, ikki xil natija olish mumkin.
Bu, ayniqsa, sahifalashda zarar keltiradi: birinchi sahifada ko'rgan hujjat ikkinchi sahifada yana chiqishi mumkin.
Yechim oddiy: saralashga yagona maydonni qo'shing:
.sort({ yil: -1, _id: 1 })
_id har doim yagona, shuning uchun tartib endi to'liq
aniqlangan bo'ladi. Bu odat tarzida qilinishi kerak -
foydali, lekin hech qanday qo'shimcha xarajat keltirmaydi.
limit va skip #
db.kitob.find({}, { nom: 1, _id: 0 }).sort({ yil: 1 }).limit(3)
db.kitob.find({}, { nom: 1, _id: 0 }).sort({ yil: 1 }).skip(3).limit(3)
[
{ nom: 'Otkan kunlar' },
{ nom: 'Mehrobdan chayon' },
{ nom: 'Sarob' }
]
[ { nom: 'Kecha va kunduz' }, { nom: 'Shum bola' }, { nom: 'Navoiy' } ]
Bu - klassik sahifalash: skip((sahifa - 1) * hajm).
skip katta sonlarda juda sekinskip(100000) "100 000-hujjatga sakra" degani emas. Baza
birinchi 100 000 hujjatni haqiqatan o'qiydi va ularni
tashlab yuboradi.
| Sahifa | O'qiladigan hujjatlar |
|---|---|
| 1 | 20 |
| 10 | 200 |
| 100 | 2 000 |
| 5000 | 100 000 |
Ya'ni sahifa raqami oshgan sari so'rov sekinlashadi. Bu
"chuqur sahifalash muammosi" deb ataladi va u faqat
MongoDB ga xos emas - SQL dagi OFFSET ham xuddi shunday
ishlaydi.
Yechim - kursor bo'yicha sahifalash: oxirgi ko'rilgan qiymatdan keyingisini so'rash. Uni quyida ko'ramiz.
Kursor bo'yicha sahifalash #
skip o'rniga "oxirgi ko'rgan qiymatdan keyin" shartini
yozamiz:
const tartib = { yil: 1, _id: 1 }
const p = { _id: 0, nom: 1, yil: 1 }
const birinchi = db.kitob.find({}, p).sort(tartib).limit(3).toArray()
birinchi.map(k => k.nom)
const oxirgi = db.kitob.findOne({ nom: birinchi[2].nom })
const keyin = { $or: [
{ yil: { $gt: oxirgi.yil } },
{ yil: oxirgi.yil, _id: { $gt: oxirgi._id } }
] }
db.kitob.find(keyin, p).sort(tartib).limit(3)
[ 'Otkan kunlar', 'Mehrobdan chayon', 'Sarob' ]
[
{ nom: 'Kecha va kunduz', yil: 1936 },
{ nom: 'Shum bola', yil: 1936 },
{ nom: 'Navoiy', yil: 1944 }
]
Bu so'rov necha-nchi sahifada bo'lishidan qat'i nazar bir xil tezlikda ishlaydi - chunki indeks kerakli joyga darhol sakraydi.
skip | Kursor bo'yicha | |
|---|---|---|
| Tezlik | Sahifa oshgani sari sekinlashadi | Doim bir xil |
| "5-sahifaga o't" | Mumkin | Mumkin emas |
| Yangi yozuv qo'shilsa | Hujjat takrorlanishi mumkin | Takrorlanmaydi |
| Qayerda ishlatiladi | Admin panel, kichik ro'yxat | Cheksiz ro'yxat, API |
Sanash #
db.kitob.countDocuments()
db.kitob.countDocuments({ janr: "tarixiy" })
db.kitob.estimatedDocumentCount()
8
4
8
countDocuments va estimatedDocumentCountIkkalasi ham son qaytaradi, lekin butunlay boshqacha ishlaydi:
countDocuments | estimatedDocumentCount | |
|---|---|---|
| Filtr qabul qiladimi | Ha | Yo'q |
| Qanday hisoblaydi | Hujjatlarni sanaydi | Metama'lumotdan o'qiydi |
| Aniqmi | Ha | Taxminiy |
| Tezligi | To'plam hajmiga bog'liq | Doimiy |
| Tranzaksiyada ishlaydimi | Ha | Yo'q |
Qachon qaysi biri:
- Sahifalash uchun "jami nechta" - aniq son kerak,
countDocuments. - Bosh sahifada "bizda 2 000 000+ mahsulot" - taxminiy
yetarli,
estimatedDocumentCount.
Uchinchi variant ham bor: agar son tez-tez kerak bo'lsa,
uni alohida hujjatda saqlab, $inc bilan yangilab boring.
Million hujjatli to'plamda countDocuments har safar
sekundlarga cho'zilishi mumkin.
Kursorni aylanib chiqish #
let jami = 0
db.kitob.find({ janr: "tarixiy" }).forEach(k => { jami += k.narx })
jami
330000
forEach hujjatlarni birma-bir oladi - xotirada bir
vaqtda faqat bitta blok turadi. Katta to'plamda toArray()
o'rniga aynan shu ishlatiladi.
Kursor metodlari #
| Metod | Nima qiladi |
|---|---|
.sort({ ... }) | Saralaydi |
.limit(n) | Eng ko'pi bilan n ta |
.skip(n) | Birinchi n tasini tashlaydi |
.toArray() | Hammasini massivga yig'adi |
.forEach(f) | Birma-bir aylanadi |
.hasNext() / .next() | Qo'lda boshqarish |
.count() | Eskirgan - countDocuments ishlating |
const kursor = db.kitob.find({ janr: "tarixiy" }).sort({ yil: 1 })
kursor.hasNext()
kursor.next().nom
kursor.next().nom
true
Otkan kunlar
Mehrobdan chayon
sortbilan narx bo'yicha kamayish tartibida saralang.- Ikkita maydon bo'yicha saralang va tartib ta'sirini ko'ring.
- Saralashga nega
_idqo'shish kerakligini yozing. limitvaskipbilan ikkinchi sahifani oling.skip(100000)nega sekin ekanini tushuntiring.- Kursor bo'yicha sahifalashni yozing.
- Ikki usulning farqini jadval qilib solishtiring.
countDocumentsvaestimatedDocumentCountni chaqiring.- Qaysi biri qachon to'g'ri kelishini yozing.
forEachbilan yig'indi hisoblang.
Xulosa #
find()hujjatlarni emas, kursor qaytaradi.- Kursor natijani bloklar bilan uzatadi - xotirada hammasi turmaydi.
sortda1o'sish,-1kamayish; bir nechta maydonda tartib muhim.- Teng qiymatlarda tartib kafolatlanmagan - saralashga
_idqo'shing. limitkursorni cheklaydi,toArray()esa xotiraga yig'adi.skipkatta sonlarda sekin: u tashlanadigan hujjatlarni ham o'qiydi.- Kursor bo'yicha sahifalash sahifa raqamidan qat'i nazar bir xil tez.
- Uning narxi: "5-sahifaga sakrash" imkoni yo'q.
countDocumentsaniq va filtr oladi;estimatedDocumentCounttez, lekin taxminiy.- Katta to'plamni aylanish uchun
toArray()emas,forEachishlating.
Keyingi bo'limda sxema dizaynini - ma'lumotni ichiga joylash yoki havola qilish masalasini ko'ramiz.
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.