MongoDB: أساسيات ومبادئ الفهرسة

آخر تحديث: 2026-08-26

تعد الفهارس عنصراً أساسياً في أداء قواعد البيانات — فإتقان مبادئ الفهارس وطرق تحسينها يمكن أن يزيد من سرعة الاستعلام بمقدار 1,000 ضعف.

1. ما ستتعلمه



2. مبادئ الفهرسة (شجرة B)

شرح المفهوم: الفهرس هو بنية بيانات مساعدة، تشبه فهرس محتويات الكتاب، تتيح لقاعدة البيانات تحديد موقع مستند ما بسرعة دون الحاجة إلى مسح المجموعة بأكملها. يستخدم MongoDB بنية فهرس قائمة على شجرة B (يستخدم محرك WiredTiger نسخة معدلة من شجرة B+) لإنشاء تخطيط مرتب بين قيم الحقول ومواقع المستندات، مما يقلل من التعقيد الزمني للاستعلامات من O(N) إلى O(log N).

كيفية العمل: تقوم فهارس B+Tree في MongoDB بتخزين قيم الحقول في العقد الداخلية لأغراض التوجيه، وتخزين قيم المفاتيح الفعلية ومؤشرات المستندات في العقد الورقية. وترتبط العقد الورقية عبر قائمة مزدوجة الارتباط، مما يدعم بطبيعة الحال استعلامات النطاق والفرز. عندما يتطابق استعلام ما مع فهرس ما، يقارن المحرك قيم المفاتيح طبقةً تلو الأخرى بدءًا من العقدة الجذرية، ليحدد في النهاية الموقع الفعلي للمستند المستهدف في إحدى العقد الورقية، وبذلك يتجنب إجراء مسح كامل للمجموعة (COLLSCAN).

حالات الاستخدام:

100%
graph TB
    A[Root Node<br/>50-100] --> B[Internal Node 1<br/>20-50]
    A --> C[Internal Node 2<br/>20-50]
    B --> D[Leaf Node 1<br/>Link to the document]
    B --> E[Leaf Node 2]
    C --> F[Leaf Node 3]
    C --> G[Leaf Node 4]
    D <--> E <--> F <--> G

    style A fill:#cce5ff
    style D fill:#d4edda
    style E fill:#d4edda
    style F fill:#d4edda
    style G fill:#d4edda

عملية الاستعلام في شجرة B+: في حالة الاستعلام عن المساواة، تُجرى المقارنات طبقةً تلو الأخرى بدءًا من العقدة الجذرية → حتى الوصول إلى عقدة ورقة → ثم يتم إرجاع مؤشر المستند؛ أما في حالة الاستعلام عن النطاق، فبعد تحديد موقع عقدة الورقة البادئة، يتم مسح القائمة المرتبطة تسلسليًّا → ويتم جمع جميع المستندات التي تستوفي المعايير.

100%
sequenceDiagram
    participant App as Application Inquiry
    participant WT as WiredTigerEngine
    participant IX as B+TreeIndex
    participant DOC as Collection of Documents

    App->>WT: find({sku: 'SKU-005'})
    WT->>IX: Root Node Comparison 50<005<100 → Zuo Zishu
    IX-->>WT: Internal Node: 20<005<50 → Left lobe
    WT->>IX: Leaf Node Search SKU-005
    IX-->>WT: Found → doc_pointer=0x7F3A
    WT->>DOC: Read 0x7F3A Location Documentation
    DOC-->>App: Return matching documents

    Note over WT,IX: Time Complexity O(log N)<br/>No need to scan all the documents
العملية المسح الكامل للجدول (COLLSCAN) فهرس شجرة B+ (IXSCAN) الفرق في الأداء
استعلام القيم المتطابقة O(N) O(log N) 100,000 صف: 100 ألف مقابل 17
استعلام النطاق O(N) O(log N + K) 100,000 سطر: 100 ألف مقابل 17 ألفًا وأكثر
الفرز O(N log N) O(log N + K) الفهرس مرتب بطبيعة الحال، لذا لا حاجة إلى الفرز
إدراج/تحديث O(1) O(log N) عبء إضافي لصيانة الفهرس

(1) تفاصيل حول تخزين الفهرس في WiredTiger

البعد الوصف
تنسيق الفهرس شجرة B+، أزواج المفتاح-القيمة مخزنة بترتيب مرتب
العقدة الورقية تحتوي على مفتاح الفهرس + معرّف السجل (مؤشر موقع المستند)
العقدة الداخلية تحتوي فقط على مفتاح المسار ومؤشر إلى عقدة فرعية
اتصال القائمة المرتبطة تشكل العقد الطرفية قائمة مرتبطة ثنائياً، مما يتيح المسح التسلسلي
الضغط ضغط البادئات يقلل من مساحة التخزين


3. بناء جملة الأمر createIndex

شرح المفهوم: createIndex() هو الأمر الأساسي لإنشاء الفهارس في MongoDB؛ حيث يوجه المحرك لإنشاء بنية فهرس من نوع B+Tree للحقل المحدد. وبمجرد إنشاء الفهرس، يقوم مُحسِّن الاستعلام تلقائيًّا بتحديد ما إذا كان سيتم استخدامه أم لا — ولا يحتاج المطورون إلى تعديل عبارة الاستعلام.

كيفية العمل: عند إنشاء فهرس، يقوم MongoDB بمسح جميع المستندات الموجودة في المجموعة، واستخراج قيم الحقول المفهرسة، وفرزها، ثم إنشاء بنية شجرة B+ التي تُسجل على القرص. خلال هذه العملية، يتم فرض قفل كتابة على المجموعة (يمكن إنشاء الفهارس في الخلفية background: true)، وقد يستغرق إنشاء فهرس لمجموعة كبيرة ما بين بضع دقائق إلى عدة ساعات.

قواعد النحو:

المعلمة النوع الوصف
keys كائن حقول الفهرس واتجاه الترتيب: 1 تصاعدي، -1 تنازلي
unique منطقية ما إذا كان الفهرس فريدًا أم لا؛ القيمة الافتراضية هي «false»
background منطقية تحديد ما إذا كان سيتم الإنشاء في الخلفية (دون إعاقة عمليات القراءة/الكتابة)؛ القيمة الافتراضية هي «false»
name سلسلة اسم فهرس مخصص؛ القيمة الافتراضية هي field_1
partialFilterExpression كائن معايير الفهرسة الجزئية (يتم فهرسة الوثائق التي تستوفي المعايير فقط)
sparse منطقية فهرس متفرق؛ تخطي الحقول ذات القيمة "null"
expireAfterSeconds الرقم مؤشر TTL، تنتهي صلاحيته ويُحذف تلقائيًا (بالثواني)
v رقم إصدار الفهرس؛ القيمة الافتراضية هي v=2
JAVASCRIPT
// === Create a single-field index ===
db.products.createIndex({ sku: 1 });          // Ascending
db.products.createIndex({ createdAt: -1 });   // Descending

// === Create a composite index ===
db.products.createIndex({ category: 1, price: -1 });

// === Create a unique index ===
db.products.createIndex({ sku: 1 }, { unique: true });

// === Create in the Backend(Non-blocking)===
db.products.createIndex({ tags: 1 }, { background: true });

// === Custom Index Name ===
db.products.createIndex({ title: 1 }, { name: 'idx_title' });

تحليل النقاط الرئيسية:

  1. تؤثر القيمتان 1 و-1 على ترتيب الفرز في الفهارس؛ ولا يكون لهما أي تأثير عملي على الفهارس أحادية الحقل، لكنهما ضروريتان لتحسين عملية الفرز في الفهارس المركبة.
  2. background: true في MongoDB 4.2 والإصدارات الأحدث، يتم تنفيذ ذلك تلقائيًا في الخلفية؛ حيث يتم الاحتفاظ بالمعلمات، لكن لم يعد من الضروري تحديدها صراحةً.
  3. تحتوي كل مجموعة على فهرس فريد افتراضي باسم _id (لا يمكن حذفه)؛ ولا داعي لإنشاء فهرس إضافي.

▶ المثال 1: createIndex ومراقبة إنشاء الفهرس

JAVASCRIPT
// ShopHub E-commerce:Create a key index for the product collection,Monitoring Build Progress
db.products.createIndex({ category: 1, price: -1, rating: -1 }, { name: 'idx_category_price_rating', background: true });

// View Index Build Progress
db.currentOp({
  $or: [
    { op: 'command', 'command.createIndexes': { $exists: true } },
    { op: 'none', ns: /shopdb\.products/ }
  ]
});

// View All Indexes
db.products.getIndexes();
// [
//   { v: 2, key: { _id: 1 }, name: '_id_' },
//   { v: 2, key: { category: 1, price: -1, rating: -1 }, name: 'idx_category_price_rating' }
// ]

الإخراج:

TEXT 📖 للعرض فقط
تم إنشاء الفهرس المركب { category: 1, price: -1, rating: -1 } بنجاح في الخلفية.
الفهارس الحالية: [{ v: 2, key: { _id: 1 }, name: '_id_' }, { v: 2, key: { category: 1, price: -1, rating: -1 }, name: 'idx_category_price_rating' }]


4. خطة تنفيذ الدالة explain()

شرح المفهوم: explain() هي أداة لتحليل الاستعلامات في MongoDB تعرض خطة التنفيذ التي اختارها مُحسِّن الاستعلامات، بما في ذلك المقاييس الرئيسية مثل ما إذا تم استخدام فهرس أم لا، وعدد المستندات التي تم فحصها، والمدة التي استغرقتها العملية. وهي بمثابة «أشعة سينية» لتحسين الفهارس — قم بتشغيل explain() أولاً، ثم قم بالتحسين.

كيفية العمل: يقوم مُحسِّن الاستعلامات في MongoDB بإنشاء عدة خطط مرشحة لكل استعلام، ثم ينفذها، ويقارن أداءها، ويخزن الخطة المثلى في ذاكرة التخزين المؤقت. explain() يُنتج ثلاثة مستويات:

100%
graph LR
    A[Query Request] --> B[Query Optimizer]
    B --> C[Generate Candidate Plans]
    C --> D[Plan A: IXSCAN]
    C --> E[Plan B: COLLSCAN]
    D --> F[Execution Comparison]
    E --> F
    F --> G[Selecting the Optimal Plan]
    G --> H[Cache + Execute]

    style G fill:#d4edda
    style E fill:#f8d7da

حالات الاستخدام:

JAVASCRIPT
// === View the query execution plan ===
db.products.find({ category: 'Electronics' }).explain('executionStats');

// === Key Metrics ===
{
  queryPlanner: {
    winningPlan: {
      stage: 'IXSCAN',           // Index Scan(✅)
      // stage: 'COLLSCAN',      // Exhaustive Scan(❌)
      inputStage: {
        stage: 'IXSCAN',
        indexName: 'category_1'
      }
    }
  },
  executionStats: {
    totalDocsExamined: 250,      // Number of scanned documents
    totalKeysExamined: 250,      // Number of Scan Index Keys
    nReturned: 250,              // Number of documents returned
    executionTimeMillis: 5,      // Execution Time(milliseconds)
    totalQueryPlanExecutionTime: 7
  }
}

(1) تفسير المؤشرات الرئيسية

المقياس القيمة المستهدفة المشكلة الوصف
stage IXSCAN COLLSCAN (مسح الجدول بالكامل) يتطلب COLLSCAN تحسين الفهرس
totalDocsExamined ≈ nReturned أكبر بكثير من nReturned تشير القيمة التي تزيد عن 10 أضعاف إلى انخفاض دقة الفهرس
totalKeysExamined ≈ nReturned أكبر بكثير من nReturned نطاق مسح الفهرس كبير جدًّا
executionTimeMillis < 50 مللي ثانية > 100 مللي ثانية النظر في تغطية الفهرس أو الفهارس المركبة
indexName اسم الفهرس المستهدف id أو لا شيء تأكيد ما إذا تم العثور على الفهرس المتوقع

مقارنة بين أوضاع الشرح الثلاثة:**

الوضع المخرجات السيناريوهات القابلة للتطبيق
'queryPlanner' التخطيط فقط، دون تنفيذ التحقق السريع من استخدام الفهرس
'executionStats' إحصائيات التخطيط والتنفيذ تحليل الأداء (الأكثر شيوعًا)
'allPlansExecution' إحصائيات جميع الخطط المرشحة تحليل اختيار المُحسِّن

▶ المثال 2: استخدام explain() لتشخيص الاستعلامات البطيئة

JAVASCRIPT
// TechCorp: The system has detected that order lookups are becoming slower. Use explain to diagnose
// Before the Index:COLLSCAN
db.orders.find({ status: 'paid', total: { $gte: 100 } }).explain('executionStats');
// stage: 'COLLSCAN', totalDocsExamined: 100000, executionTimeMillis: 450

// Create a composite index
db.orders.createIndex({ status: 1, total: -1 });

// After indexing:IXSCAN
db.orders.find({ status: 'paid', total: { $gte: 100 } }).explain('executionStats');
// stage: 'IXSCAN', indexName: 'status_1_total_-1'
// totalDocsExamined: 5000, nReturned: 5000, executionTimeMillis: 8

// Efficiency Assessment: examined/returned = 1.0 (Optimal Value), Performance improved 56x

الإخراج:

TEXT 📖 للعرض فقط
قبل الفهرس: stage: 'COLLSCAN', totalDocsExamined: 100000, executionTimeMillis: 450
بعد الفهرس: stage: 'IXSCAN', indexName: 'status_1_total_-1', totalDocsExamined: 5000, executionTimeMillis: 8
تحسين الأداء: 56x (من 450ms إلى 8ms)


5. المؤشرات المركبة والبادئة الموجودة في أقصى اليسار

شرح المفهوم: الفهرس المركب هو فهرس واحد يتم إنشاؤه عبر حقول متعددة (على سبيل المثال، { category: 1, price: -1, rating: 1 }). وهو أكثر كفاءة من الفهارس الفردية المتعددة لأن عملية بحث واحدة في الفهرس يمكن أن تستوفي شروط استعلامات متعددة في آن واحد. ويُعد مبدأ «البادئة الموجودة في أقصى اليسار» القاعدة الأساسية للفهارس المركبة — حيث لا يكون الفهرس فعالاً إلا عندما تُستخدم الحقول بالتتابع بدءًا من الحقل الموجود في أقصى اليسار.

كيفية العمل: يقوم الفهرس المركب بإنشاء شجرة B+ استنادًا إلى ترتيب الحقول. فهو يبدأ أولاً بالفرز حسب الحقل الأول؛ وإذا كانت قيم الحقل الأول متطابقة، فإنه يفرز حسب الحقل الثاني، وهكذا دواليك. وأثناء إجراء الاستعلام، يجب أن تبدأ عملية المطابقة من الحقل الموجود في أقصى اليسار؛ حيث إن تخطي أي حقول مسبقة سيجعل الحقول اللاحقة في الفهرس عديمة الفائدة — تمامًا كما يتعين عليك أولاً تحديد الحرف الأول عند البحث عن كلمة في القاموس.

100%
graph TB
    subgraph "Composite Index {category, price, rating}"
        A[Electronics<br/>$100<br/>★5] --> B[Electronics<br/>$200<br/>★4]
        B --> C[Electronics<br/>$300<br/>★3]
        C --> D[Books<br/>$10<br/>★5]
        D --> E[Books<br/>$20<br/>★4]
    end

    subgraph "Query Hit Analysis"
        F["✅ {category}"] --> G["✅ {category, price}"]
        G --> H["✅ {category, price, rating}"]
        I["❌ {price}"] --> J["Skip category"]
        K["❌ {rating}"] --> L["Skip category, price"]
        M["❌ {category, rating}"] --> N["Skip price"]
    end

    style F fill:#d4edda
    style G fill:#d4edda
    style H fill:#d4edda
    style I fill:#f8d7da
    style K fill:#f8d7da
    style M fill:#f8d7da

حالات الاستخدام:

نمط الاستعلام يطابق {الفئة، السعر، التقييم} السبب
{category: 'A'} ✅ جميع النتائج استخدم البادئة "category"
{category: 'A', price: {$gte: 100}} ✅ جميع النتائج استخدم البادئة "الفئة + السعر"
{category: 'A', price: {$gte: 100}, rating: 5} ✅ جميع النتائج تطابق تام في ثلاثة حقول
{price: {$gte: 100}} ❌ لا توجد نتائج مطابقة تخطي الفئة الموجودة في أقصى اليسار
{rating: 5} ❌ لا توجد نتائج تخطي الفئة والسعر
{category: 'A', rating: 5} ⚠️ فقط «الفئة» و«السعر» معطلان؛ أما «التقييم» فهو غير متاح
JAVASCRIPT
// === Composite Index:{ category: 1, price: -1, rating: 1 } ===
db.products.createIndex({ category: 1, price: -1, rating: 1 });

// ✅ Queries Using Indexes:
db.products.find({ category: 'Electronics' });                                    // Uses category
db.products.find({ category: 'Electronics', price: { $gte: 100 } });            // Uses category + price
db.products.find({ category: 'Electronics', price: { $gte: 100 }, rating: 5 }); // Use all 3 Field

// ⚠️ Queries That Do Not Use Indexes:
db.products.find({ price: { $gte: 100 } });                                      // Skip category
db.products.find({ rating: 5 });                                                 // Skip category, price
db.products.find({ category: 'Electronics', rating: 5 });                       // Skip price

مبدأ البادئة الموجودة في أقصى اليسار: لا تكون الفهارس المركبة فعالة إلا عند استخدامها بالتتابع بدءًا من الحقل الموجود في أقصى اليسار. وتؤدي تخطي الحقول الوسيطة إلى قطع سلسلة الفهرس — بحيث لا يمكن للحقول اللاحقة استخدام الفهرس.

قاعدة ESR (المساواة → الفرز → النطاق): القاعدة الذهبية لترتيب الحقول في الفهرس المركب — حيث تأتي حقول تصفية المساواة أولاً، وحقول الفرز في الوسط، وحقول استعلام النطاق أخيرًا. سيتم تناول هذا الموضوع بالتفصيل في درس لاحق (الدرس 19).



6. تغطية المؤشر

شرح المفهوم: يشير مصطلح «الاستعلام المغطى» إلى الاستعلام الذي تتضمنه الفهرس جميع الحقول المطلوبة لإجراء الاستعلام، مما يتيح للمحرك إرجاع النتائج مباشرةً من الفهرس دون الحاجة إلى استرجاع المستند الأصلي. ويُعد هذا الشكل الأمثل لتحسين الفهرس — حيث لا يتطلب الاستعلام أي وصول إلى بيانات المستند على الإطلاق.

كيفية العمل: تتمثل عملية الاستعلام القياسية في «مسح الفهرس → استرداد مؤشر المستند → الوصول إلى المستند في الجدول → استخراج الحقول → الإرجاع»؛ أما عملية الاستعلام القائمة على الفهرس فتتمثل في «مسح الفهرس → استخراج الحقول مباشرةً من الفهرس → الإرجاع». ومن خلال التخلص من الحاجة إلى الوصول إلى الجدول، تنخفض عمليات الإدخال/الإخراج إلى النصف، ويتحسن الأداء بنسبة تزيد عن 50%.

100%
graph LR
    subgraph "General Inquiry"
        A1[Index Scan] --> A2[Get doc pointer]
        A2 --> A3[Return to the table and read the document]
        A3 --> A4[Extract Fields]
        A4 --> A5[Return Results]
    end

    subgraph "Index-Covered Queries"
        B1[Index Scan] --> B2[Directly Extract Index Fields]
        B2 --> B3[Return Results]
    end

    style A3 fill:#f8d7da
    style B2 fill:#d4edda

حالات الاستخدام:

البعد الاستعلام العادي تغطية الفهرس
مسار التنفيذ مسح الفهرس → البحث عن المستند في الجدول مسح الفهرس → العودة مباشرة
عدد عمليات الإدخال/الإخراج 2 (الفهرس + المستند) 1 (الفهرس فقط)
الأداء المعيار أسرع بنسبة 50٪ أو أكثر
شرح المؤشر FETCH المرحلة PROJECTION_COVERED
القيود لا شيء يجب أن يستبعد العرض _id
JAVASCRIPT
// === General Inquiry:I need to go back to the table to check the documentation. ===
db.products.find(
  { category: 'Electronics' },
  { sku: 1, title: 1, price: 1 }
);

// === Index-Covered Queries:Returned directly from the index ===
db.products.createIndex({ category: 1, sku: 1, title: 1, price: 1 });

db.products.find(
  { category: 'Electronics' },
  { sku: 1, title: 1, price: 1, _id: 0 }  // _id: 0 Must be excluded
);
// explain Displayed in the middle stage: 'PROJECTION_COVERED' ✅

تحليل النقاط الرئيسية:

  1. يتم إرجاع _id دائمًا بشكل افتراضي ولا يتم تضمينه في الفهرس العادي؛ ويجب عليك استخدام _id: 0 لاستبعاده من أجل تجاوزه.
  2. كلما زاد عدد حقول الفهرس، زاد عدد الاستعلامات التي تغطيها، ولكن حجم الفهرس يزداد أيضًا — لذا يجب إجراء مقايضة بين هذين العاملين.
  3. تكون تغطية الفهرس أكثر فعالية في حالة المستندات الكبيرة (حيث يتناسب مقدار ما يتم توفيره من عمليات الإدخال/الإخراج مع حجم المستند).


7. إدارة الفهرس

شرح المفهوم: تشمل إدارة الفهارس إنشاء الفهارس وعرضها وحذفها وإعادة بنائها. في بيئة الإنتاج، لا يمكن اعتبار الفهارس أمرًا يمكن «ضبطه وتركه دون متابعة» ببساطة؛ بل يتعين عليك مراقبة استخدامها باستمرار، وحذف الفهارس غير المستخدمة، وإعادة بناء الفهارس المجزأة.

دورة حياة المؤشر:

100%
graph LR
    A[Analyzing Query Patterns] --> B[Design Index]
    B --> C[Create an Index<br/>background:true]
    C --> D[Validate Hit<br/>explain()]
    D --> E[Monitoring Utilization<br/>$indexStats]
    E --> F{Low utilization rate?}
    F -->|Yes| G[Delete Index]
    F -->|No| H[Continue monitoring]
    G --> A
    H --> E

    style G fill:#f8d7da
    style D fill:#d4edda
العمليات الإدارية القيادة الوصف
عرض الفهرس db.col.getIndexes() عرض جميع الفهارس وتعريفات المفاتيح
حذف الفهرس db.col.dropIndex(name) الحذف حسب الاسم أو تعريف المفتاح
حذف الكل db.col.dropIndexes() حذف جميع الفهارس (الاحتفاظ بـ _id)
إعادة إنشاء الفهرس db.col.reIndex() إعادة إنشاء جميع الفهارس (إزالة التجزئة)
حجم الفهرس db.col.totalIndexSize() عرض مساحة الفهرس المستخدمة (بايت)
استخدام الفهرس db.col.aggregate([{$indexStats:{}}]) عرض عدد مرات استخدام كل فهرس
JAVASCRIPT
// === View all indexes in the collection ===
db.products.getIndexes();

// === Delete Index ===
db.products.dropIndex('sku_1');
db.products.dropIndex({ sku: 1 });

// === Delete all indexes(Retain _id)===
db.products.dropIndexes();

// === Rebuild Index ===
db.products.reIndex();

// === View Index Size ===
db.products.totalIndexSize();

تحليل النقاط الرئيسية:

  1. reIndex() يقفل المجموعة؛ في بيئة الإنتاج، يُنصح بتنفيذ هذا الإجراء خلال فترة الصيانة.
  2. dropIndex() نوصي باستخدام $indexStats أولاً للتأكد من أن الفهرس غير مستخدم بالفعل.
  3. يُوصى بألا يحتوي كل فهرس محدد على أكثر من 10 إدخالات؛ حيث إن وجود عدد كبير جدًّا من الإدخالات سيؤثر على أداء الكتابة.


8. تكلفة المؤشر

شرح المفهوم: الفهارس ليست مجانية — فكل فهرس يشغل مساحة على القرص، ويزيد من عبء الكتابة، ويستهلك الذاكرة. وفهم تكلفة الفهارس هو الأساس لاتخاذ التوازنات الصحيحة. «كلما زاد عدد الفهارس، كان ذلك أفضل» هو المفهوم الخاطئ الأكثر شيوعًا.

كيفية العمل: في كل مرة يتم فيها إدراج مستند أو تحديثه أو حذفه، يتعين على MongoDB تحديث هياكل B+Tree لجميع الفهارس ذات الصلة بشكل متزامن. إذا كانت المجموعة تحتوي على N فهرسًا، فإن عمليات الكتابة تتطلب الحفاظ على N من هياكل B+Tree. وكلما زاد عدد الفهارس، زادت بطء عمليات الكتابة، وزاد استهلاك الذاكرة (نظرًا لأن ذاكرة التخزين المؤقتة لـ WiredTiger يجب أن تقوم بتحميل صفحات الفهرس).

100%
graph LR
    A[Create an index] --> B[Faster reads]
    A --> C[Slower writes]
    A --> D[Takes up space]

    B --> B1[Search +1000x]
    C --> C1[Insert/Update +50% Expenses]
    D --> D1[Per Index 1-10 MB]

    subgraph "Weighing Decisions"
        E[High query frequency?] -->|Yes| F[✅ Create an index]
        E -->|No| G[❌ Do not build]
        H[High write frequency?] -->|Yes| I[⚠️ Caution]
        H -->|No| F
    end

تحديد تكلفة الفهرسة:

البعد المتعلق بالتكلفة التأثير القياس الكمي
مساحة القرص يشغل كل فهرس ما يقارب 5–20% من حجم البيانات 10 جيجابايت من البيانات × 8 فهارس ≈ 4–16 جيجابايت من المساحة الإضافية
زمن استجابة الكتابة كل فهرس إضافي يزيد من وقت الكتابة بنسبة ~5–10% 8 فهارس → تصبح عمليات الكتابة أبطأ بنسبة 40–80%
استخدام الذاكرة تتطلب ذاكرة التخزين المؤقت لـ WiredTiger تحميل صفحات الفهرس انخفاض أداء الاستعلامات عند عدم تخزين الفهرس في ذاكرة التخزين المؤقت
تكاليف الصيانة العمليات مثل إعادة الفهرسة، والمراقبة، وإعادة البناء كلما زاد عدد الفهارس، زادت تعقيدات الصيانة

المفاضلات في المؤشرات:

استراتيجية اختيار المؤشر:

السيناريو التوصية السبب
البحث عن المنتجات في التجارة الإلكترونية المؤشر المركب للفئة والسعر التصفية والفرز عالي التردد
تسجيل دخول المستخدم فهرس البريد الإلكتروني الفريد الربط المتساوي + قيد التفرّد
استعلام السجل مؤشر TTL لـ createdAt النطاق الزمني + انتهاء الصلاحية التلقائي
حقل الحالة (انتقائية منخفضة) لا يُنصح بإنشائه بشكل منفصل لا تحتوي الخاصية isActive إلا على قيمتين، لذا فإن كفاءة الفهرسة منخفضة للغاية
البحث عن النص فهرس النص للبحث عن النص الكامل فقط


9. التدريب العملي الشامل

(1) مبادئ تصميم الفهرس

شرح مفصل لقاعدة ESR: يجب أن يتبع ترتيب الحقول في الفهرس المركب التسلسل التالي: Equality (المساواة) → Sort (الفرز) → Range (النطاق). ضع حقل تصفية المساواة أولاً (لتضييق النطاق بسرعة)، وحقل الفرز في المنتصف (للاستفادة من ترتيب الفهرس وتجنب الفرز داخل الذاكرة)، وحقل استعلام النطاق أخيرًا (نظرًا لأن مسح النطاق سيؤدي إلى مقاطعة استخدام حقول الفهرس اللاحقة).

100%
graph LR
    E["Equality<br/>Equivalence Filtering<br/>category='A'"] --> S["Sort<br/>Sort<br/>createdAt: -1"]
    S --> R["Range<br/>Scope<br/>price >= 100"]

    style E fill:#d4edda
    style S fill:#cce5ff
    style R fill:#fff3cd
ترتيب الفهرس كفاءة الاستعلام السبب
{E, S, R} ⭐⭐⭐ الأمثل المطابقة الدقيقة → الفرز حسب الفهرس → المسح النطاقي
{E, R, S} ⭐⭐ جيد تحديد المواقع بالتكافؤ → مسح النطاق → الفرز حسب الذاكرة
{R, S, E} ⭐ ضعيف نطاق المسح واسع جدًّا، مما يؤدي إلى فقدان ميزة القيم المتساوية
JAVASCRIPT
// === Design of an E-commerce Product Index ===
db.products.createIndex({ sku: 1 }, { unique: true });                    // Unique Index
db.products.createIndex({ category: 1, price: -1 });                       // Category Page
db.products.createIndex({ category: 1, rating: -1 });                      // Categories+Rating
db.products.createIndex({ isActive: 1, createdAt: -1 });                   // Listing Date
db.products.createIndex({ title: 'text', description: 'text' });          // Full-Text Search
db.products.createIndex({ tags: 1 });                                      // Tag Filtering

(2) تحليل استخدام المؤشر

JAVASCRIPT
// === Analyzing Slow Queries ===
db.products.find({
  category: 'Electronics',
  price: { $gte: 100, $lte: 1000 },
  isActive: true
}).sort({ createdAt: -1 }).limit(20);

// Check whether an index is being used
const explain = db.products.find({...}).explain('executionStats');
print('Stage:', explain.queryPlanner.winningPlan.stage);
print('Docs Examined:', explain.executionStats.totalDocsExamined);
print('Time:', explain.executionStats.executionTimeMillis, 'ms');

▶ مثال: التصميم العملي للمؤشرات وتحليل الأداء

JAVASCRIPT
// 1. Create a Test Set(10 10,000 product records)
for (let i = 0; i < 100000; i++) {
  db.products.insertOne({
    sku: 'SKU-' + i.toString().padStart(6, '0'),
    title: 'Product ' + i,
    category: ['Electronics', 'Books', 'Clothing', 'Home'][i % 4],
    price: Math.random() * 1000,
    stock: Math.floor(Math.random() * 100),
    createdAt: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000),
    isActive: true
  });
}

// 2. Create an Index
db.products.createIndex({ sku: 1 }, { unique: true });
db.products.createIndex({ category: 1, price: -1 });  // Composite Index
db.products.createIndex({ createdAt: -1 });

// 3. Comparison:Indexed vs No index
console.time('Unindexed Query');
db.products.find({ category: 'Electronics', price: { $gte: 100, $lte: 500 } }).toArray();
console.timeEnd('Unindexed Query');  // ~500ms

// After creating the index
db.products.createIndex({ category: 1, price: 1 });

console.time('Indexed queries');
db.products.find({ category: 'Electronics', price: { $gte: 100, $lte: 500 } }).toArray();
console.timeEnd('Indexed queries');  // ~5ms(Performance ↑100x)

// 4. explain() Analyze the Execution Plan
const explain = db.products.find({
  category: 'Electronics',
  price: { $gte: 100, $lte: 500 }
}).sort({ createdAt: -1 }).limit(20).explain('executionStats');

print('Stage:', explain.queryPlanner.winningPlan.stage);  // IXSCAN
print('Index:', explain.queryPlanner.winningPlan.inputStage?.indexName);  // category_1_price_1
print('Docs Examined:', explain.executionStats.totalDocsExamined);
print('Keys Examined:', explain.executionStats.totalKeysExamined);
print('Returned:', explain.executionStats.nReturned);
print('Time:', explain.executionStats.executionTimeMillis, 'ms');

// 5. Index-Covered Queries(No need to return to the table)
db.products.createIndex({ category: 1, sku: 1, price: 1 });

db.products.find(
  { category: 'Electronics' },
  { sku: 1, price: 1, _id: 0 }  // Return only fields that are already in the index
).explain();

// Stage: PROJECTION_COVERED(No need to consult the documentation)

الإخراج:

TEXT 📖 للعرض فقط
الاستعلام غير المفهرس: ~500ms
الاستعلام المفهرس: ~5ms (تحسين الأداء بمقدار 100x)
Stage: IXSCAN, Index: category_1_price_1
Stage: PROJECTION_COVERED (تغطية الفهرس الكاملة)

النتيجة: يعمل الفهرس على تحسين أداء الاستعلام بمقدار 100 ضعف؛ ويُظهر EXPLAIN() IXSCAN (مسح الفهرس) + PROJECTION_COVERED (تغطية الفهرس).


❓ أسئلة شائعة

س هل من الأفضل وجود عدد أكبر من الفهارس؟
ج لا. فالفهارس تسرع عمليات القراءة، لكنها تبطئ عمليات الكتابة وتشغل مساحة. وبشكل عام، يُعد وجود 5 إلى 10 فهارس لكل مجموعة عددًا مناسبًا.
س هل ترتيب الحقول في الفهرس المركب مهم؟
ج إنه مهم جدًّا. ضع الحقول ذات الانتقائية العالية (التي تحتوي على العديد من القيم الفريدة) في البداية، وفقًا لمبدأ ESR (المساواة → الفرز → النطاق).
س لماذا لا يعمل الفهرس؟
ج الأسباب الشائعة: (1) ترتيب الحقول لا يتطابق مع البادئة الموجودة في أقصى اليسار؛ (2) استخدام $ne أو $nin أو $exists؛ (3) عدم تطابق أنواع البيانات؛ (4) المجموعة تحتوي على عدد قليل جدًا من السجلات (لا يتم تحسين الأداء إذا كان عدد السجلات أقل من 100).
س كيف يمكن تحسين أداء COLLSCAN في explain()؟
ج قم بتحليل شروط الاستعلام وإنشاء فهارس على أعمدة التصفية. ضع في اعتبارك استخدام فهارس مركبة تغطي جميع شروط التصفية.

📖 ملخص


📝 تمارين

  1. السؤال الأساسي (⭐): أنشئ ثلاثة فهارس — sku، وcategory، وcreatedAt — لمجموعة المنتجات.
  2. السؤال الأساسي (⭐): استخدم explain() لتحليل استعلام والتأكد من استخدام فهرس (IXSCAN).
  3. تمرين متقدم (⭐⭐): أنشئ فهرسًا مركبًا { category: 1, price: -1 } لاختبار مبدأ البادئة الموجودة في أقصى اليسار (أي الاستعلامات التي تستخدم الفهرس).
  4. مشكلة متقدمة (⭐⭐): قم بتنفيذ استعلام مغطى بالفهرس (حيث تكون جميع الحقول مدرجة في الفهرس).
  5. سؤال التحدي (⭐⭐⭐): صمم نظام فهرسة كامل لمنتجات التجارة الإلكترونية (8 فهارس)، وقم بتحليل سيناريوهات الاستعلام لكل فهرس.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%