MongoDB: أنماط مونجوس المتقدمة: الأداء والمرونة

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

أنماط مونجوس المتقدمة — إتقان الميزات المتقدمة مثل populate وdiscriminator وlean وaggregate.

1. ما ستتعلمه


100%
graph TB
    A[mongoose Advanced Features] --> B[populate<br/>Application Layer JOIN]
    A --> C[discriminator<br/>Single Set, Multiple Schema]
    A --> D[lean<br/>Performance Optimization]
    A --> E[aggregate<br/>Database-Level Aggregation]
    A --> F[Schema Index<br/>Declarative]

    B --> B1[Multiple queries<br/>N+1 Risks]
    C --> C1[role Field<br/>Classification by Type]
    D --> D1[Skip hydrate<br/>↑5x Performance]
    E --> E1[$facet/$lookup<br/>Return Multiple Dimensions in a Single Operation]

    style D fill:#d4edda

2. ملء الاستعلامات المدمجة

ما هو populate؟ populate هو تطبيق Mongoose لـ «JOIN على مستوى طبقة التطبيق» — فعندما يحتوي المخطط على حقل ref من النوع ObjectId، يقوم populate تلقائيًا بإصدار استعلام إضافي لاستبدال ObjectId المشار إليه بالوثيقة الكاملة. ويشمل ذلك أساسًا استعلامين: أولًا، استرداد ObjectId من المستند الأساسي، ثم استرداد البيانات الكاملة من المستند المرتبط.

المبادئ الأساسية لـ populate:

100%
sequenceDiagram
    participant App as Node.js Applications
    participant Mongo as MongoDB

    App->>Mongo: 1st query: Order.find()
    Mongo-->>App: [order1, order2, ...]
    App->>Mongo: 2nd query: User.find({_id: {$in: [ObjectId1, ObjectId2, ...]}})
    Mongo-->>App: Back users
    App->>App: Merge: order.userId → user Object

مقارنة بين دالة populate ودالة $lookup:

البعد التعبئة $lookup
طبقة التنفيذ طبقة التطبيق (استعلامان أو أكثر) طبقة قاعدة البيانات (عملية تجميع واحدة)
عدد الاستعلامات مخاطر N+1 1
المرونة متوسطة (تدعم إشارات ObjectId فقط) عالية (ارتباطات شرطية تعسفية)
الأداء مناسب لمجموعات البيانات الصغيرة موصى به لمجموعات البيانات الكبيرة
إيجاز الكود مرتفع (سطر واحد من .populate()) منخفض (صيغة مسار التجميع)
نوع القيمة المرتجعة مستند مونغوس كائن عادي

مشكلة N+1: عند الاستعلام عن 100 طلب واستخدام populate على كل order.userId، فإن ذلك يؤدي إلى إجراء 1 (استعلام عن الطلب) + 100 (استعلامات المستخدم) = 101 استعلامًا. يقوم Mongoose تلقائيًا بتحسين ذلك إلى استعلام دفعي $in (1+1=2 استعلامات)، ولكن عمليات populate المتداخلة قد تولد استعلامات إضافية.

حالات الاستخدام: بالنسبة للعلاقات الثنائية (مثل استرداد المستخدم المرتبط بأحد الطلبات)، استخدم populate؛ أما بالنسبة للعلاقات الجماعية ذات الشروط المعقدة، فاستخدم $lookup؛ ولأغراض العرض فقط، استخدم lean مقترناً بـ populate.

(1) التعبئة الأساسية

JAVASCRIPT
// === Basic populate ===
const user = await User.findById(userId).populate('addresses');
// SELECT u.*, a.* FROM users u LEFT JOIN addresses a ON u._id = a.userId

// === Nested populate ===
const order = await Order.findById(orderId)
  .populate('userId')
  .populate({
    path: 'items.productId',
    select: 'sku title price'
  });

// === Conditions populate ===
const orders = await Order.find()
  .populate({
    path: 'userId',
    match: { isActive: true },
    select: 'username avatar'
  });
populate مقابل $lookup populate $lookup
طبقة التنفيذ طبقة التطبيق طبقة قاعدة البيانات
عدد الاستعلامات N+1 1
المرونة متوسطة عالية
الأداء مناسب لمجموعات البيانات الصغيرة موصى به لمجموعات البيانات الكبيرة


3. جهاز التمييز

ما هو «المميّز»؟ المميّز هو آلية «الوراثة لمجموعة واحدة» في Mongoose — حيث تتشارك نماذج متعددة في نفس مجموعة MongoDB، مع تمييز أنواع المستندات بواسطة مفتاح مميّز. وهذا يشبه مفهوم الوراثة في البرمجة الموجهة للكائنات: حيث تحدد الفئة الأساسية الحقول المشتركة، وتوسّع الفئات الفرعية هذه الحقول بحقول محددة، وتوجد جميع المثيلات في نفس الجدول.

الآلية الأساسية للمميّز:

100%
graph TB
    subgraph "users Gathering(Single Set)"
        D1["{role: 'Customer', loyaltyPoints: 100, email: 'alice@...'}"]
        D2["{role: 'Customer', loyaltyPoints: 50, email: 'bob@...'}"]
        D3["{role: 'Admin', permissions: ['manage'], email: 'admin@...'}"]
    end

    subgraph "mongoose Model Layer"
        User[User Model<br/>email + username + passwordHash]
        Customer[Customer Model<br/>+ loyaltyPoints + preferredCategories]
        Admin[Admin Model<br/>+ permissions + lastLoginAt]
    end

    User -->|discriminator| Customer
    User -->|discriminator| Admin
    Customer -->|Search: role='Customer'| D1
    Customer -->|Search: role='Customer'| D2
    Admin -->|Search: role='Admin'| D3

    style User fill:#fff3cd
    style Customer fill:#d4edda
    style Admin fill:#cce5ff

المميّز مقابل المجموعة المستقلة:

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

حالات الاستخدام: أدوار المستخدمين (العميل/المسؤول/المشرف — تتشارك في البريد الإلكتروني وكلمة المرور، مع وجود حقول محددة لكل منها)؛ طرق الدفع (بطاقة الائتمان/باي بال/التحويل المصرفي — تتشارك في المبلغ والحالة، مع وجود حقول خاصة بكل قناة)؛ أنواع الإشعارات (البريد الإلكتروني/الرسائل النصية القصيرة/الإشعارات الفورية — تتشارك في الموضوع والمحتوى، مع وجود إعدادات خاصة بكل قناة).

JAVASCRIPT
// === Basics User Model ===
const UserSchema = new mongoose.Schema({
  email: String,
  username: String,
  passwordHash: String
}, { discriminatorKey: 'role' });

const User = mongoose.model('User', UserSchema);

// === Customer Discriminator ===
const Customer = User.discriminator('Customer', new mongoose.Schema({
  loyaltyPoints: { type: Number, default: 0 },
  preferredCategories: [String]
}));

// === Admin Discriminator ===
const Admin = User.discriminator('Admin', new mongoose.Schema({
  permissions: [String],
  lastLoginAt: Date
}));

// === Create Different Roles ===
const customer = await Customer.create({
  email: 'alice@example.com',
  username: 'alice',
  passwordHash: '...',
  loyaltyPoints: 100,
  preferredCategories: ['Electronics']
});

const admin = await Admin.create({
  email: 'admin@example.com',
  username: 'admin',
  passwordHash: '...',
  permissions: ['manage_products']
});

// === When querying, based on role Category ===
const customers = await Customer.find();
const admins = await Admin.find();
// All data is in the same collection(users),Through discriminatorKey Category

حالة الاستخدام: مجموعة واحدة تحتوي على مخططات متعددة (حقول مختلفة للأدوار المختلفة).



4. تحسين أداء دالة lean()

ما هي وظيفة lean()؟ lean() هي طريقة لتحسين الأداء في Mongoose — فهي تتخطى عملية تهيئة المستند (hydration) وتُرجع كائن JavaScript عاديًا مباشرةً. في حين أن الاستعلامات العادية تُرجع مستند Mongoose (مع طرق مثل save() وvalidate()، بالإضافة إلى تتبع التغييرات)، فإن lean() تُرجع كائنًا عاديًا (يحتوي على البيانات فقط، دون أي طرق).

المبادئ الكامنة وراء الاختلافات في الأداء في lean():

100%
graph LR
    subgraph "General Query (without lean)"
        T1[Product.find] --> D1[mongoose Document<br/>with save/validate etc.<br/>~150ms / 100 docs]
    end

    subgraph "lean Search"
        Q2[MongoDB Original BSON] --> H2[JSON.parse Direct Conversion]
        H2 --> D2[plain object<br/>Raw Data<br/>~30ms / 100 docs]
    end

    style D1 fill:#f8d7da
    style D2 fill:#d4edda

بيانات مقارنة الأداء (100 مستند، فئة الإلكترونيات):

العملية بدون منهجية «اللين» مع منهجية «اللين» مضاعف الأداء
وقت الاستعلام ~150 مللي ثانية ~30 مللي ثانية ↑5x
استهلاك الذاكرة ~5 ميغابايت ~1 ميغابايت ↓5x
JSON.stringify ~8 مللي ثانية ~2 مللي ثانية ↑4x
يدعم save()
يدعم التعبئة ✅ (المكالمات المتسلسلة)
دعم ميزة «تتبع التغييرات»

إرشادات الاستخدام: استخدم lean() لواجهات برمجة التطبيقات (API) المخصصة للقراءة فقط (القوائم، التفاصيل)؛ لا تستخدم lean() إذا كنت بحاجة إلى استدعاء save() أو تعديل التتبع؛ أضف lean() إذا لم تكن هناك حاجة إلى مزيد من التعديلات بعد populate.

JAVASCRIPT
// === General Inquiry:Back mongoose Document ===
const products = await Product.find();
// Each product is a Mongoose Document (with save() and other methods)

// === lean():Return to Pure JS Object ===
const products = await Product.find().lean();
// Each product is a plain object, Performance up 3-5x

// === Comparison Test ===
console.time('without lean');
const a = await Product.find({ category: 'Electronics' }).limit(100);
console.timeEnd('without lean');  // ~150ms

console.time('with lean');
const b = await Product.find({ category: 'Electronics' }).lean().limit(100);
console.timeEnd('with lean');  // ~30ms


5. مسار التجميع Model.aggregate()

أنابيب التجميع في Mongoose: يستدعي Model.aggregate() محرك التجميع في MongoDB مباشرةً لإجراء عمليات التجميع والربط والحسابات على مستوى قاعدة البيانات — على عكس populate الذي يتولى معالجة هذه العمليات على مستوى التطبيق، ولا تحتاج البيانات الموجودة في أنابيب التجميع إلى نقلها إلى جانب Node.js للمعالجة، مما يؤدي إلى تحسين الأداء.

دليل الاختيار بين aggregate وpopulate:

السيناريو الحل الموصى به السبب
البحث عن الطلب + اسم المستخدم التعبئة علاقة بسيطة، كود نظيف
حساب متوسط السعر لكل فئة التجميع يجب تجميع الحسابات
ضم + تجميع + فرز تجميع + $lookup كل ذلك في خطوة واحدة
عمليات الربط المتداخلة متعددة المستويات التجميع + عمليات $lookup المتعددة تجنب مشكلة N+1
إرجاع نتائج متعددة الأبعاد aggregate + $facet إرجاع طرق عرض متعددة في آن واحد

عملية تنفيذ مسار التجميع:

100%
graph LR
    Input[1,000,000 Documents] -->|"$match"| Filter[After filtering: 500,000]
    Filter -->|"$group"| Group[Group by category<br/>5 groups]
    Group -->|"$sort"| Sorted[Sort by count]
    Sorted -->|"$limit"| Output[Top 5]
    
    style Input fill:#f8d7da
    style Output fill:#d4edda
JAVASCRIPT
// === mongoose Using Aggregation in ===
const stats = await Product.aggregate([
  { $match: { isActive: true } },
  { $group: { _id: '$category', count: { $sum: 1 }, avgPrice: { $avg: '$price' } } },
  { $sort: { count: -1 } }
]);

// === aggregate + populate(mongoose 6+)===
const results = await Order.aggregate([
  { $match: { status: 'paid' } },
  {
    $lookup: {
      from: 'users',
      localField: 'userId',
      foreignField: '_id',
      as: 'customer'
    }
  },
  { $unwind: '$customer' }
]);


6. إعلان فهرس المخطط

طرق إعلان الفهارس في Mongoose: يدعم Mongoose إنشاء الفهارس بشكل إعلاني ضمن تعريفات المخطط — الفهارس على مستوى الحقول (index: true)، والفهارس المركبة (Schema.index())، والفهارس الخاصة (الفهارس النصية، وفهارس TTL، والفهارس الجزئية). وتتمثل ميزة الفهارس التصريحية في أنها تُعرَّف جنبًا إلى جنب مع المخطط، مما يجعلها سهلة الفهم بنظرة واحدة؛ كما يتم إنشاؤها تلقائيًا عند بدء التشغيل (autoIndex=true).

أنواع الفهارس وحالات الاستخدام:

نوع الفهرس طريقة الإعلان حالات الاستخدام المعلمات الخاصة
فهرس أحادي المجال { sku: { index: true } } استعلامات المساواة، والفرز فريد
المؤشر المركب Schema.index({a:1, b:-1}) الاستعلام متعدد الشروط قاعدة ESR
فهرس النص { title: { text: true } } البحث عن النص الكامل الأوزان
مؤشر TTL Schema.index({at:1}, {expireAfterSeconds:86400}) انتهاء الصلاحية التلقائي وقت انتهاء الصلاحية
فهرس جزئي partialFilterExpression فهرس الشروط شروط التصفية
الفهرس الجغرافي { loc: { type: '2dsphere' } } البحث الجغرافي

قاعدة ESR (المساواة - الترتيب - النطاق): يجب أن يتبع ترتيب الحقول في الفهرس المركب الترتيب التالي: شروط المساواة → شروط الترتيب → شروط النطاق. على سبيل المثال، يدعم { category: 1, price: -1 } find({category:'E'}) + sort({price:-1})، لكنه لا يدعم الترتيب حسب السعر وحده.

JAVASCRIPT
const ProductSchema = new mongoose.Schema({
  sku: { type: String, index: true, unique: true },
  title: { type: String, text: true },  // Text Index
  price: { type: Number, index: true },
  category: { type: String, index: true }
});

// === Composite Index ===
ProductSchema.index({ category: 1, price: -1 });

// === Selected Indexes ===
ProductSchema.index(
  { category: 1 },
  { partialFilterExpression: { isActive: true } }
);

// === TTL Index ===
ProductSchema.index(
  { createdAt: 1 },
  { expireAfterSeconds: 30 * 24 * 60 * 60 }
);


7. تحسين أداء Mongoose 7.x

منهجية Mongoose لتحسين الأداء: لا يقتصر تحسين الأداء على مجرد «إضافة استدعاء lean() واعتبار الأمر منتهياً»؛ بل يتضمن ضبطًا منهجيًّا بدءًا من طبقة الاتصال → طبقة الاستعلام → طبقة التطبيق → طبقة النشر. وتتمثل المبادئ الأساسية في: تقليل حجم نقل البيانات، وتقليل عدد الاستعلامات إلى أدنى حد، وتقليل عبء التسلسل، والاستفادة من الإمكانات الأصلية لقاعدة البيانات.

مصفوفة استراتيجيات التحسين:

طبقة التحسين الاستراتيجية الفعالية التدخل
طبقة الاتصال ضبط معلمة maxPoolSize تقليل وقت انتظار الاتصال منخفض (التكوين)
طبقة الاستعلام اختيار الإسقاط (select()) يقلل نقل البيانات بنسبة تزيد عن 90% منخفض
مستوى الاستعلام الفهرس + hint() تجنب المسح الكامل للجدول متوسط
طبقة الاستعلام lean() يقلل من العبء الإضافي الناتج عن الهيدرات بمقدار 5 أضعاف منخفض
طبقة التطبيق التوازي باستخدام Promise.all يقلل من وقت الانتظار التسلسلي منخفض
طبقة التطبيق عمليات الكتابة المجمعة (bulkWrite) تقلل عدد رحلات الذهاب والإياب عبر الشبكة بمقدار 10 أضعاف ↑ متوسطة
طبقة التطبيق تدفق المؤشر تجنب تجاوز سعة الذاكرة متوسط
مستوى النشر autoIndex=false تشغيل أسرع منخفض
طبقة النشر الفصل بين القراءة والكتابة يقلل من الحمل على العقدة الرئيسية مرتفع

ممارسات تشارلي في مجال التحسين: تم تحسين أداء واجهة برمجة تطبيقات (API) قائمة منتجات شركة TechCorp من 3 ثوانٍ إلى 50 مللي ثانية — ① تمت إضافة فهارس مركبة لتجنب استخدام COLLSCAN؛ ② تم استخدام select() للاستعلام عن 5 حقول فقط؛ ③ تم استخدام lean() لتخطي hydrate؛ ④ استخدام Promise.all لتشغيل find وcount بالتوازي؛ ⑤ تعيين حد أقصى قدره 100 صف.

JAVASCRIPT
// === Optimization 1:Disable autoIndex(Production)===
mongoose.connect(uri, { autoIndex: false });
// Manually create indexes at startup:await Product.syncIndexes();

// === Optimization 2:Bulk Operations ===
await Product.bulkWrite([
  { updateOne: { filter: { sku: 'A' }, update: { $inc: { stock: -1 } } } },
  { updateOne: { filter: { sku: 'B' }, update: { $inc: { stock: -1 } } } }
]);

// === Optimization 3:Projection Reduces Data Transmission ===
const products = await Product.find()
  .select('sku title price')  // Search only 3 field
  .lean();

// === Optimization 4:Usage cursor Streaming Processing of Big Data ===
const cursor = Product.find().cursor();
for await (const doc of cursor) {
  // Process each document
}

// === Optimization 5:Bulk Insert ===
await Product.insertMany(docs, { ordered: false });


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

JAVASCRIPT
// === Optimized List API ===
app.get('/api/products', async (req, res) => {
  const { page = 1, limit = 20, category, search } = req.query;

  // 1. Build a Query
  const query = { isActive: true };
  if (category) query.category = category;
  if (search) query.$text = { $search: search };

  // 2. Parallel Queries(find + count)
  const [products, total] = await Promise.all([
    Product.find(query)
      .select('sku title price thumbnail rating')  // Projection
      .sort({ createdAt: -1 })
      .limit(+limit)
      .skip((page - 1) * limit)
      .lean(),  // Performance Optimization
    Product.countDocuments(query)
  ]);

  res.json({
    success: true,
    data: products,
    meta: { page: +page, limit: +limit, total, pages: Math.ceil(total / limit) }
  });
});

▶ المثال 1: إنشاء علاقات متعددة المستويات + تحسين الأداء وفقًا لمبادئ «اللين»

JAVASCRIPT
// === Scene:ShopHub Order Details API(3 Layer Association)===
const mongoose = require('mongoose');

// Schemas
const AddressSchema = new mongoose.Schema({ city: String, country: String, zipCode: String });
const UserSchema = new mongoose.Schema({
  email: String, username: String,
  addresses: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Address' }]
});
const ProductSchema = new mongoose.Schema({ sku: String, title: String, price: Number, thumbnail: String });
const OrderSchema = new mongoose.Schema({
  orderNumber: String,
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  items: [{ productId: { type: mongoose.Schema.Types.ObjectId, ref: 'Product' }, qty: Number, price: Number }],
  status: String
}, { timestamps: true });

const Address = mongoose.model('Address', AddressSchema);
const User = mongoose.model('User', UserSchema);
const Product = mongoose.model('Product', ProductSchema);
const Order = mongoose.model('Order', OrderSchema);

// 3-level populate + lean
const order = await Order.findById('647f1f77bcf86cd799439001')
  .populate({ path: 'userId', select: 'username email',
    populate: { path: 'addresses', select: 'city country' }
  })
  .populate({ path: 'items.productId', select: 'sku title price' })
  .lean();

console.log({
  orderNumber: order.orderNumber,
  customer: order.userId.username,
  city: order.userId.addresses[0]?.city,
  items: order.items.map(i => `${i.productId.title} x${i.qty}`)
});
// Output:{ orderNumber: 'ORD-001', customer: 'alice', city: 'San Francisco',
//         items: ['Smartphone X x2', 'Laptop Pro x1'] }

الناتج: 3 طبقات من populate (الطلب → المستخدم → العنوان + الطلب → المنتج) + تحسين أداء lean().

▶ المثال 2: تطبيق عملي شامل لـ «Populate» + «Discriminator» + «Lean»

JAVASCRIPT
// === 1. populate Multi-level relationships ===
// Order + User + Products(3 Nested Layers)
const order = await Order.findById(orderId)
  .populate({
    path: 'userId',
    select: 'username email avatar',
    populate: { path: 'addresses', select: 'city country' }  // Address under the user
  })
  .populate({
    path: 'items.productId',
    select: 'sku title price thumbnail'
  })
  .lean();  // Performance Optimization

console.log('Order:', {
  orderNumber: order.orderNumber,
  customer: {
    username: order.userId.username,
    address: order.userId.addresses[0]?.city
  },
  items: order.items.map(i => ({
    product: i.productId.title,
    qty: i.qty,
    price: i.price
  }))
});

// === 2. discriminator Single Set, Multiple Schema ===
// User Base Class
const UserSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  username: String,
  passwordHash: String,
  createdAt: { type: Date, default: Date.now }
}, { discriminatorKey: 'role' });

const User = mongoose.model('User', UserSchema);

// Customer Discriminator(Inheritance User + Extended Fields)
const Customer = User.discriminator('Customer', new mongoose.Schema({
  loyaltyPoints: { type: Number, default: 0 },
  preferredCategories: [String],
  totalSpent: mongoose.Schema.Types.Decimal128
}));

// Admin Discriminator
const Admin = User.discriminator('Admin', new mongoose.Schema({
  permissions: [String],
  lastLoginAt: Date
}));

// Create Different Roles(All exist users Gathering,Through role Field Delimiter)
const customer = await Customer.create({
  email: 'alice@example.com',
  username: 'alice',
  passwordHash: '...',
  loyaltyPoints: 100,
  preferredCategories: ['Electronics']
});

const admin = await Admin.create({
  email: 'admin@example.com',
  username: 'admin',
  passwordHash: '...',
  permissions: ['manage_products', 'manage_users']
});

// When querying, based on role Automatic Filtering
const customers = await Customer.find({ loyaltyPoints: { $gt: 50 } });
// Actual Query:{ role: 'Customer', loyaltyPoints: { $gt: 50 } }

// === 3. lean() Performance Optimization Comparison ===
console.time('without lean');
const a = await Product.find({ category: 'Electronics' }).limit(100);
console.timeEnd('without lean');  // ~150ms

console.time('with lean');
const b = await Product.find({ category: 'Electronics' }).lean().limit(100);
console.timeEnd('with lean');  // ~30ms(5x Performance Improvements)

// === 4. Model.aggregate() Database-Level Aggregation ===
const stats = await Product.aggregate([
  { $match: { isActive: true } },
  {
    $facet: {
      totalCount: [{ $count: 'count' }],
      byCategory: [
        { $group: { _id: '$category', count: { $sum: 1 }, avgPrice: { $avg: '$price' } } },
        { $sort: { count: -1 } }
      ],
      topRated: [
        { $sort: { rating: -1 } },
        { $limit: 5 },
        { $project: { sku: 1, title: 1, rating: 1 } }
      ]
    }
  }
]);

// Output:
// {
//   totalCount: [{ count: 1250 }],
//   byCategory: [
//     { _id: 'Electronics', count: 450, avgPrice: 599 },
//     { _id: 'Books', count: 380, avgPrice: 29 },
//     ...
//   ],
//   topRated: [
//     { sku: 'PHONE-001', title: 'Smartphone X', rating: 4.9 },
//     ...
//   ]
// }

النتائج: populate يُنفذ الارتباطات متعددة المستويات؛ وdiscriminator يدعم أدوارًا متعددة ضمن مجموعة واحدة؛ وlean() يحقق زيادة في الأداء بمقدار 5 أضعاف؛ وaggregate يُجري عملية تجميع واحدة على مستوى قاعدة البيانات لإرجاع نتائج متعددة الأبعاد.

▶ مثال 3: تحسين أداء استعلامات المنتجات باستخدام الفهرس المركب وlean(الصعوبة ⭐⭐)

JAVASCRIPT
// تحسين واجهة برمجة تطبيقات قائمة المنتجات لـ ShopHub
const mongoose = require('mongoose');

// تعريف المخطط مع الفهارس المركبة
const ProductSchema = new mongoose.Schema({
  sku: { type: String, unique: true, index: true },
  title: { type: String, text: true },
  price: Number,
  category: String,
  isActive: { type: Boolean, default: true },
  stock: { type: Number, default: 0 },
  rating: { type: Number, default: 0 }
}, { timestamps: true });

// فهرس مركب للاستعلامات الشائعة
ProductSchema.index({ category: 1, isActive: 1, createdAt: -1 });
ProductSchema.index({ price: 1, rating: -1 });

const Product = mongoose.model('Product', ProductSchema);

// الاستعلام المحسن
async function getProductsOptimized(filters = {}) {
  const { category, minPrice, maxPrice, page = 1, limit = 20 } = filters;

  // بناء الاستعلام
  const query = { isActive: true };
  if (category) query.category = category;
  if (minPrice !== undefined || maxPrice !== undefined) {
    query.price = {};
    if (minPrice !== undefined) query.price.$gte = minPrice;
    if (maxPrice !== undefined) query.price.$lte = maxPrice;
  }

  // تنفيذ الاستعلام المحسن
  const [products, total] = await Promise.all([
    Product.find(query)
      .select('sku title price thumbnail rating')  // إسقاط الحقول
      .hint({ category: 1, isActive: 1, createdAt: -1 })  // إجبار الفهرس
      .sort({ createdAt: -1 })
      .skip((page - 1) * limit)
      .limit(limit)
      .lean(),  // تخطي الترطيب
    Product.countDocuments(query)
  ]);

  return { products, total, page, limit, pages: Math.ceil(total / limit) };
}

// اختبار الأداء
console.time('محسن');
const result = await getProductsOptimized({ category: 'Electronics', minPrice: 100 });
console.timeEnd('محسن');
// النتيجة: ~30 مللي ثانية

console.log(`تم العثور على ${result.total} منتج`);
console.log(`الصفحة ${result.page} من ${result.pages}`);

الإخراج:

TEXT 📖 للعرض فقط
محسن: 32ms
تم العثور على 150 منتج
الصفحة 1 من 8

❓ أسئلة شائعة

س متى ينبغي استخدام populate مقابل $lookup؟
ج استخدم populate لمجموعات البيانات الصغيرة (لضمان المرونة على مستوى طبقة التطبيق) و$lookup لمجموعات البيانات الكبيرة (لضمان الكفاءة على مستوى طبقة قاعدة البيانات).
س هل هناك فرق بين المُميِّز والمجموعة؟
ج تتشارك المُميِّزات نفس المجموعة (التي يتم تمييزها بحقل «الدور»)، في حين أن المجموعات المستقلة معزولة ماديًّا.
س هل يمكنني استدعاء save() بعد lean()؟
ج لا. تُرجع lean() كائنًا عاديًا لا يحتوي على طرق Mongoose. إذا كنت بحاجة إلى استدعاء save()، فقم بالاستعلام عن المستند مرة أخرى أو استخدم طرق المستند.

📖 ملخص


📝 تمارين

  1. المشكلة الأساسية (⭐): تنفيذ استعلام ربط populate (الطلب + المستخدم + المنتج).
  2. المشكلة الأساسية (⭐): استخدم lean() لتحسين واجهة برمجة تطبيقات قائمة المنتجات ومقارنة الفروق في الأداء.
  3. مشكلة متقدمة (⭐⭐): استخدم مُميِّزًا لتنفيذ الأدوار الثلاثة: المستخدم، والعميل، والمسؤول.
  4. تمرين متقدم (⭐⭐): استخدم bulkWrite لتحديث مخزون المنتجات بشكل جماعي (للتعامل مع حالات نفاد المخزون).
  5. التحدي (3 نجوم): إكمال واجهات برمجة التطبيقات المتقدمة لـ Mongoose (populate + lean + aggregate + discriminator).
Web-Tutorial.com

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

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

100%