MongoDB: أساسيات ومبادئ الفهرسة
آخر تحديث: 2026-08-26
تعد الفهارس عنصراً أساسياً في أداء قواعد البيانات — فإتقان مبادئ الفهارس وطرق تحسينها يمكن أن يزيد من سرعة الاستعلام بمقدار 1,000 ضعف.
1. ما ستتعلمه
- مبادئ الفهرسة (بنية بيانات شجرة B)
- بناء جملة createIndex()
- الفهارس أحادية العمود والفهارس المركبة
- تفسير خطة التنفيذ لـ explain()
- تغطية الفهرس (الاستعلام المشمول)
- تكاليف المؤشرات والمفاضلات
2. مبادئ الفهرسة (شجرة B)
شرح المفهوم: الفهرس هو بنية بيانات مساعدة، تشبه فهرس محتويات الكتاب، تتيح لقاعدة البيانات تحديد موقع مستند ما بسرعة دون الحاجة إلى مسح المجموعة بأكملها. يستخدم MongoDB بنية فهرس قائمة على شجرة B (يستخدم محرك WiredTiger نسخة معدلة من شجرة B+) لإنشاء تخطيط مرتب بين قيم الحقول ومواقع المستندات، مما يقلل من التعقيد الزمني للاستعلامات من O(N) إلى O(log N).
كيفية العمل: تقوم فهارس B+Tree في MongoDB بتخزين قيم الحقول في العقد الداخلية لأغراض التوجيه، وتخزين قيم المفاتيح الفعلية ومؤشرات المستندات في العقد الورقية. وترتبط العقد الورقية عبر قائمة مزدوجة الارتباط، مما يدعم بطبيعة الحال استعلامات النطاق والفرز. عندما يتطابق استعلام ما مع فهرس ما، يقارن المحرك قيم المفاتيح طبقةً تلو الأخرى بدءًا من العقدة الجذرية، ليحدد في النهاية الموقع الفعلي للمستند المستهدف في إحدى العقد الورقية، وبذلك يتجنب إجراء مسح كامل للمجموعة (COLLSCAN).
حالات الاستخدام:
- الحقول التي تُستخدم بشكل متكرر كمعايير للبحث (مثل
skuوuserId) - حقل الفرز (على سبيل المثال،
createdAt: -1) - الحقول التي تتطلب قيدًا فريدًا (على سبيل المثال،
email) - غير مناسب لـ: الحقول ذات الانتقائية المنخفضة (مثل
isActive، التي لا تقبل سوى القيم «صحيح» أو «خطأ»)، والحقول التي يتم الكتابة فيها بشكل متكرر جدًّا ولكن نادرًا ما يتم الاستعلام عنها
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+: في حالة الاستعلام عن المساواة، تُجرى المقارنات طبقةً تلو الأخرى بدءًا من العقدة الجذرية → حتى الوصول إلى عقدة ورقة → ثم يتم إرجاع مؤشر المستند؛ أما في حالة الاستعلام عن النطاق، فبعد تحديد موقع عقدة الورقة البادئة، يتم مسح القائمة المرتبطة تسلسليًّا → ويتم جمع جميع المستندات التي تستوفي المعايير.
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 |
// === 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 على ترتيب الفرز في الفهارس؛ ولا يكون لهما أي تأثير عملي على الفهارس أحادية الحقل، لكنهما ضروريتان لتحسين عملية الفرز في الفهارس المركبة.
background: trueفي MongoDB 4.2 والإصدارات الأحدث، يتم تنفيذ ذلك تلقائيًا في الخلفية؛ حيث يتم الاحتفاظ بالمعلمات، لكن لم يعد من الضروري تحديدها صراحةً.- تحتوي كل مجموعة على فهرس فريد افتراضي باسم
_id(لا يمكن حذفه)؛ ولا داعي لإنشاء فهرس إضافي.
▶ المثال 1: createIndex ومراقبة إنشاء الفهرس
// 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() يُنتج ثلاثة مستويات:
queryPlanner: الخطة التي اختارها مُحسِّن الأداء (لم يتم تنفيذها)executionStats: إحصائيات التنفيذ الفعلية (يتطلب المعلمة'executionStats')allPlansExecution: إحصائيات التنفيذ لجميع الخطط المرشحة
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
حالات الاستخدام:
- إذا كان الاستعلام بطيئًا، فاستخدم
explain()للتحقق مما إذا كان قد استخدم فهرسًا - قبل نشر فهرس جديد، تحقق مما إذا كانت الاستعلامات تُرجع نتائج
- مقارنة الاختلافات في الأداء بين أنظمة الفهرسة المختلفة
- تبين أن
COLLSCAN(المسح الكامل للجدول) يحتاج إلى تحسين
// === 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() لتشخيص الاستعلامات البطيئة
// 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+ استنادًا إلى ترتيب الحقول. فهو يبدأ أولاً بالفرز حسب الحقل الأول؛ وإذا كانت قيم الحقل الأول متطابقة، فإنه يفرز حسب الحقل الثاني، وهكذا دواليك. وأثناء إجراء الاستعلام، يجب أن تبدأ عملية المطابقة من الحقل الموجود في أقصى اليسار؛ حيث إن تخطي أي حقول مسبقة سيجعل الحقول اللاحقة في الفهرس عديمة الفائدة — تمامًا كما يتعين عليك أولاً تحديد الحرف الأول عند البحث عن كلمة في القاموس.
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 + price) - تركيبات التصفية + الفرز (على سبيل المثال،
status = 'paid'+sort by createdAt) - غير مناسب: الاستعلام عن كل شرط على حدة (وفي هذه الحالة، تكون الفهارس متعددة الحقول الفردية أكثر مرونة)
| نمط الاستعلام | يطابق {الفئة، السعر، التقييم} | السبب |
|---|---|---|
{category: 'A'} |
✅ جميع النتائج | استخدم البادئة "category" |
{category: 'A', price: {$gte: 100}} |
✅ جميع النتائج | استخدم البادئة "الفئة + السعر" |
{category: 'A', price: {$gte: 100}, rating: 5} |
✅ جميع النتائج | تطابق تام في ثلاثة حقول |
{price: {$gte: 100}} |
❌ لا توجد نتائج مطابقة | تخطي الفئة الموجودة في أقصى اليسار |
{rating: 5} |
❌ لا توجد نتائج | تخطي الفئة والسعر |
{category: 'A', rating: 5} |
⚠️ فقط «الفئة» و«السعر» معطلان؛ أما «التقييم» فهو غير متاح |
// === 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%.
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
حالات الاستخدام:
- الاستعلامات المتكررة لا تتطلب سوى عدد قليل من الحقول (على سبيل المثال، لا تعرض صفحة القائمة سوى
sku, title, price) - مجموعة كبيرة من المستندات؛ تكلفة عالية لعمليات البحث
- الشرط الأساسي: يجب أن يستبعد الإسقاط
_id(_id: 0)، ويجب أن تكون جميع حقول الإسقاط موجودة في الفهرس
| البعد | الاستعلام العادي | تغطية الفهرس |
|---|---|---|
| مسار التنفيذ | مسح الفهرس → البحث عن المستند في الجدول | مسح الفهرس → العودة مباشرة |
| عدد عمليات الإدخال/الإخراج | 2 (الفهرس + المستند) | 1 (الفهرس فقط) |
| الأداء | المعيار | أسرع بنسبة 50٪ أو أكثر |
| شرح المؤشر | FETCH المرحلة |
PROJECTION_COVERED |
| القيود | لا شيء | يجب أن يستبعد العرض _id |
// === 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' ✅
تحليل النقاط الرئيسية:
- يتم إرجاع
_idدائمًا بشكل افتراضي ولا يتم تضمينه في الفهرس العادي؛ ويجب عليك استخدام_id: 0لاستبعاده من أجل تجاوزه. - كلما زاد عدد حقول الفهرس، زاد عدد الاستعلامات التي تغطيها، ولكن حجم الفهرس يزداد أيضًا — لذا يجب إجراء مقايضة بين هذين العاملين.
- تكون تغطية الفهرس أكثر فعالية في حالة المستندات الكبيرة (حيث يتناسب مقدار ما يتم توفيره من عمليات الإدخال/الإخراج مع حجم المستند).
7. إدارة الفهرس
شرح المفهوم: تشمل إدارة الفهارس إنشاء الفهارس وعرضها وحذفها وإعادة بنائها. في بيئة الإنتاج، لا يمكن اعتبار الفهارس أمرًا يمكن «ضبطه وتركه دون متابعة» ببساطة؛ بل يتعين عليك مراقبة استخدامها باستمرار، وحذف الفهارس غير المستخدمة، وإعادة بناء الفهارس المجزأة.
دورة حياة المؤشر:
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:{}}]) |
عرض عدد مرات استخدام كل فهرس |
// === 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();
تحليل النقاط الرئيسية:
reIndex()يقفل المجموعة؛ في بيئة الإنتاج، يُنصح بتنفيذ هذا الإجراء خلال فترة الصيانة.dropIndex()نوصي باستخدام$indexStatsأولاً للتأكد من أن الفهرس غير مستخدم بالفعل.- يُوصى بألا يحتوي كل فهرس محدد على أكثر من 10 إدخالات؛ حيث إن وجود عدد كبير جدًّا من الإدخالات سيؤثر على أداء الكتابة.
8. تكلفة المؤشر
شرح المفهوم: الفهارس ليست مجانية — فكل فهرس يشغل مساحة على القرص، ويزيد من عبء الكتابة، ويستهلك الذاكرة. وفهم تكلفة الفهارس هو الأساس لاتخاذ التوازنات الصحيحة. «كلما زاد عدد الفهارس، كان ذلك أفضل» هو المفهوم الخاطئ الأكثر شيوعًا.
كيفية العمل: في كل مرة يتم فيها إدراج مستند أو تحديثه أو حذفه، يتعين على MongoDB تحديث هياكل B+Tree لجميع الفهارس ذات الصلة بشكل متزامن. إذا كانت المجموعة تحتوي على N فهرسًا، فإن عمليات الكتابة تتطلب الحفاظ على N من هياكل B+Tree. وكلما زاد عدد الفهارس، زادت بطء عمليات الكتابة، وزاد استهلاك الذاكرة (نظرًا لأن ذاكرة التخزين المؤقتة لـ WiredTiger يجب أن تقوم بتحميل صفحات الفهرس).
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 (النطاق). ضع حقل تصفية المساواة أولاً (لتضييق النطاق بسرعة)، وحقل الفرز في المنتصف (للاستفادة من ترتيب الفهرس وتجنب الفرز داخل الذاكرة)، وحقل استعلام النطاق أخيرًا (نظرًا لأن مسح النطاق سيؤدي إلى مقاطعة استخدام حقول الفهرس اللاحقة).
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} |
⭐ ضعيف | نطاق المسح واسع جدًّا، مما يؤدي إلى فقدان ميزة القيم المتساوية |
// === 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) تحليل استخدام المؤشر
// === 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');
▶ مثال: التصميم العملي للمؤشرات وتحليل الأداء
// 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(تغطية الفهرس).
❓ أسئلة شائعة
explain()؟📖 ملخص
- مبدأ الفهرسة: بنية بيانات شجرة B، عدد الاستعلامات O(log N)
- صيغة createIndex: حقل واحد، مركب، فريد، نصي
- تفسير دالة explain(): winningPlan + executionStats
- مبدأ البادئة الموجودة في أقصى اليسار بالنسبة للفهارس المركبة
- تغطية الفهرس: تعرض النتائج مباشرةً من الفهرس، مما يغني عن البحث في الجدول
- العبء الإضافي للفهرس: بطء عملية الكتابة + استهلاك المساحة
📝 تمارين
- السؤال الأساسي (⭐): أنشئ ثلاثة فهارس — sku، وcategory، وcreatedAt — لمجموعة المنتجات.
- السؤال الأساسي (⭐): استخدم
explain()لتحليل استعلام والتأكد من استخدام فهرس (IXSCAN). - تمرين متقدم (⭐⭐): أنشئ فهرسًا مركبًا { category: 1, price: -1 } لاختبار مبدأ البادئة الموجودة في أقصى اليسار (أي الاستعلامات التي تستخدم الفهرس).
- مشكلة متقدمة (⭐⭐): قم بتنفيذ استعلام مغطى بالفهرس (حيث تكون جميع الحقول مدرجة في الفهرس).
- سؤال التحدي (⭐⭐⭐): صمم نظام فهرسة كامل لمنتجات التجارة الإلكترونية (8 فهارس)، وقم بتحليل سيناريوهات الاستعلام لكل فهرس.