تطوير الويب

Indexes MongoDB: single، composite، multikey، partial، TTL وقاعدة ESR

2 min de lecture

السلسلة: هذا الدرس جزء من سلسلة MongoDB. اقرأ المقال الرئيسي.

index MongoDB يُحوّل قراءة O(n) إلى O(log n). الفرق بين طلب في 12 مللي ثانية ونفس الطلب في 4 ثوان على نفس collection. القاعدة الكونية: كل حقل مُفلتر، مُرتَّب، أو مُستخدَم كمفتاح jointure يستحق index. هذا الدرس يفصّل العائلات السبع لـ indexes MongoDB 8.0 LTS، قاعدة ESR (Equality، Sort، Range)، وقراءة explain().

المتطلبات

  • MongoDB 8.0 LTS، mongosh أو Compass
  • Collection اختبار بـ 100 000 وثيقة على الأقل
  • 90 دقيقة

الخطوة 1 — لماذا يُغيّر index كل شيء

// بلا index — explain() يُظهر COLLSCAN
db.commandes.find({ statut: "payee" }).explain("executionStats");
// "executionTimeMillis": 1842, "totalDocsExamined": 1000000

// إنشاء index على الحقل المُفلتَر
db.commandes.createIndex({ statut: 1 });

// مع index — explain() يُظهر IXSCAN
db.commandes.find({ statut: "payee" }).explain("executionStats");
// "executionTimeMillis": 12, "totalDocsExamined": 24500

القيمة 1 في كائن index هي الترتيب الصاعد. للطلبات بالمساواة، الاتجاه بلا أهمية؛ للترتيبات، يصير حرجاً.

الخطوة 2 — Index بسيط على حقل واحد

// Index على حقل مُفلتر متكرر
db.produits.createIndex({ categorie: 1 });

// Index مع قيد فريد — يرفض الازدواجيات
db.utilisateurs.createIndex({ email: 1 }, { unique: true });

// Index على حقل متداخل
db.produits.createIndex({ "attributs.marque": 1 });

// قائمة indexes الموجودة
db.produits.getIndexes();

إنشاء index فريد على حقل موجود يفشل إن احتوت collection ازدواجيات: E11000 duplicate key error.

الخطوة 3 — Index مركّب وقاعدة ESR

ترتيب الحقول في index ليس عشوائياً: يجب اتباع قاعدة ESR — Equality first، Sort second، Range last.

// طلب نموذجي: كل مستخدمين paying من مدينة X، مُرتَّبون بالتاريخ
db.utilisateurs.find({
  ville: "Riyadh",                              // égalité
  abonnement: "actif",                          // égalité
  inscrit_le: { $gte: ISODate("2026-01-01") }   // range
}).sort({ inscrit_le: -1 });

// Index يحترم ESR: égalités → tri → range
db.utilisateurs.createIndex(
  { ville: 1, abonnement: 1, inscrit_le: -1 }
);

ترتيب ESR يُعظّم معدّل التصفية. عكسه (range قبل égalité) يُجبر المحرّك على scan الفرع كاملاً والترتيب بعد القراءة.

الخطوة 4 — Multikey index على مصفوفات

// وثيقة نموذجية بمصفوفة tags
// { _id, titre, tags: ["mongodb", "tutoriel", "performance"] }

db.articles.createIndex({ tags: 1 });    // multikey آلي

// طلبات مغطّاة
db.articles.find({ tags: "mongodb" });
db.articles.find({ tags: { $all: ["mongodb", "perf"] } });
db.articles.find({ tags: { $in: ["mongodb", "redis"] } });

القيد: index مركّب لا يستطيع فهرسة إلا حقل مصفوفة واحد بين مفاتيحه.

الخطوة 5 — Partial index

// على 10 ملايين مستخدم، 50 000 فقط premium
db.utilisateurs.createIndex(
  { expire_le: 1 },
  { partialFilterExpression: { plan: "premium" } }
);

// الطلب الذي يستخدم index — يجب أن يحوي المعيار partiel
db.utilisateurs.find({
  plan: "premium",
  expire_le: { $lte: new Date() }
});

// الطلب الذي لا يستخدم index — بلا "plan: premium"
db.utilisateurs.find({ expire_le: { $lte: new Date() } });

القاعدة الذهبية: الطلب يجب أن يحوي نفس clause الـ partialFilterExpression لاستخدام index. إن نسيت، COLLSCAN صامت.

الخطوة 6 — TTL index للانتهاء الآلي

// Sessions تنتهي 24 ساعة بعد الإنشاء
db.sessions.createIndex(
  { cree_le: 1 },
  { expireAfterSeconds: 86400 }
);

// Logs محفوظة 30 يوماً
db.logs.createIndex(
  { date: 1 },
  { expireAfterSeconds: 2592000 }
);

TTL monitor يُنفَّذ كل 60 ثانية. الحذف ليس فورياً عند الانتهاء لكن يصل في الدقيقة التي تلي. الحقل المُفهرس يجب أن يكون من نوع Date.

الخطوة 7 — Text index للبحث plein-texte

// Index text على العنوان والوصف
db.produits.createIndex(
  { nom: "text", description: "text" },
  { default_language: "french", weights: { nom: 5, description: 1 } }
);

// بحث plein-texte
db.produits.find(
  { $text: { $search: "ordinateur portable" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } });

// بحث دقيق
db.produits.find({ $text: { $search: "\"cle USB\"" } });

// استثناء كلمة
db.produits.find({ $text: { $search: "ordinateur -reconditionne" } });

القيد: collection لا يمكنها امتلاك إلا index text واحد، حتى إن غطّى عدة حقول. لاحتياجات متقدمة (أخطاء كتابة، lemmes)، Atlas Search أو Elasticsearch.

الخطوة 8 — explain(): القراءة التي تحسم

db.commandes.find(
  { statut: "payee", cree_le: { $gte: ISODate("2026-01-01") } }
).sort({ cree_le: -1 }).explain("executionStats");

// 5 حقول للقراءة:
// 1. winningPlan.stage              -> IXSCAN أو COLLSCAN (الهدف: IXSCAN)
// 2. winningPlan.inputStage.indexName -> اسم index المُستخدَم
// 3. executionStats.executionTimeMillis -> المدة الكلية
// 4. executionStats.totalKeysExamined  -> عدد إدخالات index مقروءة
// 5. executionStats.totalDocsExamined  -> عدد وثائق مُجسَّدة

النسبة للمراقبة: nReturned / totalDocsExamined. إن أرجعت 100 نتيجة وفحصت 100 000 وثيقة، index لا يُميّز بما يكفي.

الخطوة 9 — صيانة ونظافة

// إحصاءات استخدام indexes
db.commandes.aggregate([{ $indexStats: {} }]);

// المخرَج: { name, key, accesses: { ops, since } }
// Index بـ accesses.ops = 0 منذ أسابيع = مرشّح للحذف

// حذف index باسمه
db.commandes.dropIndex("statut_1_cree_le_-1");

// إعادة بناء (مفيد بعد فساد أو oplog طويل)
db.commandes.reIndex();

القاعدة العملية: راجع $indexStats فصلياً واحذف ما لم يخدم. كل index جديد يجب أن يُبرَّر بطلب ملموس، لا «احتياطاً».

أخطاء شائعة

الخطأ السبب الحل
طلب بطيء رغم index ترتيب حقول لا يتبع ESR أعد الترتيب: égalités → tri → range
E11000 duplicate key Index فريد على حقل بازدواجيات نظّف الازدواجيات قبل الإنشاء
TTL لا يطهّر الحقل ليس Date BSON حوّل عبر سكربت migration
Multikey مستحيل على مصفوفتين قيد المحرّك denormalize: حقل scalaire رئيسي
Index text غير مُستخدَم عدة indexes text غير متوافقة collection لها index text واحد فقط
RAM مشبَّعة Index أكبر من working set Index partiel أو sharding

أسئلة شائعة

كم index لكل collection؟ MongoDB يأذن حتى 64. عملياً 5-12 يكفي لـ 95% من الطلبات.

هل نُفهرس _id؟ MongoDB ينشئ آلياً index فريداً على _id. غير قابل للحذف.

Index صاعد أم نازل؟ بلا أهمية للطلبات بالمساواة أو $in. حرج للترتيبات.

إنشاء index في الإنتاج بلا downtime؟ MongoDB 4.2+ ينشئ indexes خلفياً افتراضياً.

متى ننتقل إلى Atlas Search؟ حين يصير index text محدوداً: تحمّل الأخطاء، مترادفات، autocompletion. Atlas Search مشمول على M0 بحدود اختبار.

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité