MongoDB: تحديث المستند: شرح مفصل لـ `updateOne` و`updateMany`

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

يُعد تحديث المستندات إحدى عمليات الكتابة الأكثر شيوعًا في MongoDB — ويُعد إتقان استخدام المُعدِّل update أمرًا أساسيًّا لتعديل البيانات.

تقدم هذه الدورة استكشافًا متعمقًا لـ updateOne وupdateMany وreplaceOne، ومُعدِّلات التحديث المختلفة ($set و$inc و$push و$pull)، وسلوك upsert، وضمانات الترابطية.

1. ما ستتعلمه



2. قصة حقيقية عن منصة للتجارة الإلكترونية

(1) المشكلة: مشكلات التزامن عند خصم المخزون

تتولى أليس إدارة نظام الطلبات في إحدى شركات التجارة الإلكترونية، حيث واجهت مشكلة تقليدية تتعلق بالتزامن أثناء تحديث المخزون:

JAVASCRIPT
// ❌ Counterexample:Check First, Then Edit(Competitive Conditions)
app.post('/api/orders', async (req, res) => {
  const product = await Product.findOne({ sku: 'PHONE-001' });

  if (product.stock <= 0) {
    return res.status(400).json({ error: 'Out of stock' });
  }

  // ⚠️ Concurrency Issues Here:Both requests were read stock=1
  await Product.updateOne(
    { sku: 'PHONE-001' },
    { $inc: { stock: -1 } }
  );
  // Both requests were successfully deducted,As a result, the inventory became -1
});

(2) حلول للتحديثات الذرية في MongoDB

JAVASCRIPT
// ✅ Correct Example:Usage + Atomic Manipulation
app.post('/api/orders', async (req, res) => {
  const result = await Product.updateOne(
    { sku: 'PHONE-001', stock: { $gt: 0 } },  // Key:Conditional Filtering
    { $inc: { stock: -1 } }
  );

  if (result.modifiedCount === 0) {
    return res.status(400).json({ error: 'Out of stock' });
  }
  // Only the following was changed: 1 This document indicates success
});

(3) الإيرادات

البعد البحث ثم التحديث التحديث المتكامل
أمان التزامن ❌ حالات التنافس ✅ العمليات الذرية
الأداء ⚠️ استعلامان ⚡ عملية واحدة
تعقيد الكود مرتفع منخفض


3. updateOne: تحديث مستند واحد

شرح المفهوم: updateOne هي طريقة التحديث الأكثر استخدامًا في MongoDB؛ حيث تقوم بمطابقة المستند الأول بناءً على شروط التصفية وتطبق عملية التحديث عليه. وهي تشبه UPDATE ... SET ... WHERE ... في لغة SQL، لكن MongoDB تستخدم مُعدِّلات التحديث (مثل $set و$inc) لتحديد العناصر المطلوب تعديلها، بدلاً من استبدال المستند بأكمله. ويجعل هذا التصميم عمليات التحديث الجزئي أكثر كفاءة — حيث يتم تعديل الحقول التي تم تغييرها فقط، بدلاً من إعادة كتابة المستند بأكمله.

كيفية العمل: updateOne يتبع مسار التنفيذ الخطوات التالية: مرحلة المطابقة (تحديد موقع المستندات بناءً على معيار التصفية) → مرحلة التحديث (تطبيق مُعدِّلات التحديث) → تحديث الفهرس (في حالة تعديل حقول الفهرس) → تأكيد «Write Concern». وتُعتبر العملية برمتها عملية متكاملة بالنسبة للمستند الواحد — فلا توجد حالة وسيطة يتم فيها «تحديث نصف الحقول فقط».

100%
sequenceDiagram
    participant App as Applications
    participant Mongo as MongoDB
    participant WT as WiredTiger

    App->>Mongo: updateOne({ sku: "PHONE-001" }, { $set: { price: 699 } })
    Mongo->>Mongo: Match filter (Using Indexes)
    Mongo->>Mongo: Applications $set Edit
    Mongo->>Mongo: Check whether the index needs to be updated
    Mongo->>WT: Save the modified document
    WT-->>Mongo: Confirm
    Mongo-->>App: { matchedCount: 1, modifiedCount: 1 }
المعلمة النوع الوصف
filter المستند معايير البحث (إلزامية)
update مستند عملية تحديث (إلزامية؛ يجب أن تتضمن مُعدِّلًا)
options مستند upsert / writeConcern وما إلى ذلك (اختياري)
حقل الإرجاع المعنى ملاحظات
matchedCount عدد الوثائق المطابقة قد يكون 0
modifiedCount العدد الفعلي للوثائق التي تم تعديلها 0 إذا كانت القيمة هي نفسها ولم تتغير
upsertedCount عدد المستندات التي تم إدراجها عبر عملية «upsert» قد يكون 1 فقط عندما تكون قيمة «upsert» هي «true»

(1) قواعد النحو الأساسية

JAVASCRIPT
// === updateOne Basic Usage ===
db.products.updateOne(
  { sku: "PHONE-001" },        // filter
  { $set: { price: 699.99 } }  // update
);

الإخراج:

TEXT 📖 للعرض فقط
{
  acknowledged: true,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0,
  upsertedId: null
}

(2) تفسير قيم الإرجاع

JAVASCRIPT
const result = await Product.updateOne(
  { sku: 'PHONE-001' },
  { $set: { stock: 50 } }
);

result.acknowledged;   // true(Write confirmed)
result.matchedCount;    // 1(Found 1 matches)
result.modifiedCount;   // 1(Actual Changes 1 items)
result.upsertedCount;   // 0(Not inserted)

الإخراج:

TEXT 📖 للعرض فقط
true
1
1
0
الحقل المعنى
matchedCount عدد الوثائق التي تتطابق مع معايير التصفية
modifiedCount العدد الفعلي للوثائق التي تم تعديلها
upsertedCount عدد المستندات التي تم إدراجها عبر عملية «upsert»
upsertedId تم إدراج _id في المستند

(3) التعامل مع حالات عدم وجود تطابق

JAVASCRIPT
const result = await Product.updateOne(
  { sku: 'NOT_EXIST' },
  { $set: { stock: 0 } }
);
print(result.matchedCount);   // 0
print(result.modifiedCount);  // 0
// No errors,Do not modify

الإخراج:

TEXT 📖 للعرض فقط
0
0

▶ المثال 1: تطبيق وظيفة updateOne عمليًّا

JAVASCRIPT
// === Edit a Single Field ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { price: 699.99 } }
);

// === Edit Multiple Fields ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  {
    $set: {
      price: 699.99,
      stock: 50,
      lastUpdated: new Date()
    }
  }
);

// === Updating Nested Fields ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { "specs.battery": "5000mAh" } }
);

// === mongoose Equivalent Notation ===
const result = await Product.updateOne(
  { sku: 'PHONE-001' },
  { $set: { price: 699.99, lastUpdated: new Date() } }
);

الإخراج:

TEXT 📖 للعرض فقط
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }


4. updateMany: التحديث الجماعي

شرح المفهوم: يطابق updateMany جميع المستندات التي تستوفي المعايير ويطبق عملية التحديث بشكل موحد على جميعها. وعلى عكس updateOne، الذي يقتصر تعديله على أول نتيجة مطابقة فقط، فإن updateMany قادر على تحديث عشرات الآلاف من المستندات دفعة واحدة. وهذه هي الطريقة الأساسية لإجراء التعديلات المجمعة (مثل الخصومات على مستوى الموقع، وحذف العناصر بالجملة، وإصلاح البيانات).

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

البعد التحديث الفردي التحديث الجماعي
نطاق التطابق أول تطابق جميع التطابقات
العمليات الجماعية ❌ فردي ✅ جماعي
التراجع عن المعاملة ❌ غير مدعوم ❌ غير مدعوم
حالات الاستخدام التعديلات الفردية الخصومات الجماعية، وحذف المنتجات من القائمة، وإعادة إدراجها
المخاطر منخفضة متوسطة (تأثير كبير ناجم عن أخطاء المستخدم)

(1) قواعد النحو الأساسية

JAVASCRIPT
// === updateMany Basic Usage ===
db.products.updateMany(
  { category: 'Electronics' },   // filter(Multiple matches)
  { $set: { discount: 0.1 } }    // update(Batch Application)
);

الإخراج:

TEXT 📖 للعرض فقط
{
  acknowledged: true,
  matchedCount: 250,
  modifiedCount: 250,
  upsertedCount: 0
}

(2) ملاحظات حول التحديثات الجماعية

JAVASCRIPT
// ⚠️ updateMany Transaction rollback is not supported
// If it fails along the way,Changes that have already been made will not be rolled back.

// ⚠️ Bulk updates may lock the collection
// We recommend using batch size control:
const BATCH_SIZE = 1000;
let modified = 0;
let lastId = null;

while (true) {
  const result = await Product.updateMany(
    {
      category: 'Electronics',
      _id: { $gt: lastId }
    },
    { $set: { onSale: true } },
    { limit: BATCH_SIZE }  // mongoose option
  );
  if (result.modifiedCount === 0) break;
  modified += result.modifiedCount;
}

الإخراج:

TEXT 📖 للعرض فقط
{ acknowledged: true, matchedCount: 1000, modifiedCount: 1000 }
{ acknowledged: true, matchedCount: 1000, modifiedCount: 1000 }
{ acknowledged: true, matchedCount: 500, modifiedCount: 500 }
Total modified: 2500 documents

▶ المثال 2: دليل عملي للتحديثات المجمعة

JAVASCRIPT
// === Apply 10% off to all Electronics products ===
db.products.updateMany(
  { category: 'Electronics' },
  { $mul: { price: 0.9 } }
);

// === Remove all expired products from the shelves ===
db.products.updateMany(
  { expiryDate: { $lt: new Date() } },
  { $set: { isActive: false } }
);

// === To everyone 5 Add tags to products with star ratings ===
db.products.updateMany(
  { rating: { $gte: 4.8 } },
  { $addToSet: { tags: 'top-rated' } }
);

الإخراج:

TEXT 📖 للعرض فقط
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 50, modifiedCount: 45 }
{ acknowledged: true, matchedCount: 25, modifiedCount: 20 }


5. replaceOne: استبدال المستند بأكمله

شرح المفهوم: يتمثل الاختلاف الأساسي بين replaceOne وupdateOne في أن updateOne يقوم بتحديث الحقول المحددة في المُعدِّل مع الحفاظ على الحقول غير المحددة؛ أما replaceOne، فيستبدل محتوى المستند بالكامل، ويتم حذف الحقول غير المحددة. وتعد هذه العملية من أخطر العمليات في MongoDB، وقد يؤدي سوء استخدامها إلى فقدان البيانات.

حالات الاستخدام: استخدم replaceOne فقط عندما تحتاج إلى إعادة كتابة مستند بالكامل (على سبيل المثال، لترحيل البيانات أو تحديث تنسيق المستند). وفي معظم الحالات، استخدم updateOne + $set لتعديل الحقول الضرورية فقط.

البعد updateOne + $set replaceOne
حقل غير محدد ✅ الاحتفاظ ❌ الحذف
الوحدة ✅ وحدة المستند الواحد ✅ وحدة المستند الواحد
حالات الاستخدام تعديل حقول محددة إعادة كتابة المستند بأكمله
المخاطر منخفضة عالية (فقدان الحقل)

(1) الاختلاف عن updateOne

JAVASCRIPT
// === updateOne:Modify only the specified fields ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { price: 699 } }
);
// Results:{ _id, sku, title, price: 699, stock, category, ... }(Keep the other fields)

// === replaceOne:Replace throughout the document ===
db.products.replaceOne(
  { sku: 'PHONE-001' },
  { sku: 'PHONE-001', title: 'New Phone', price: 799 }
);
// Results:{ _id, sku, title: 'New Phone', price: 799 }
// ⚠️ Other Fields(stock、category etc.)All Lost!

(2) حالات الاستخدام لـ replaceOne

JAVASCRIPT
// ✅ Applicable:Completely rewrite the document
db.users.replaceOne(
  { _id: 'user_001' },
  {
    _id: 'user_001',
    name: 'Alice',
    email: 'alice@example.com',
    role: 'admin',
    updatedAt: new Date()
  }
);

// ❌ Not applicable:I just want to modify one field(use updateOne + $set)


6. مُعدِّلات تحديث الحقول

شرح المفهوم: تعد مُعدِّلات التحديث هي البنية الأساسية لعمليات التحديث في MongoDB، وهي تحدد كيفية تعديل حقول المستند. على عكس SET field = value في SQL، توفر MongoDB مجموعة غنية من المُعدِّلات—$set (تعيين القيمة)، $unset (حذف الحقل)، $inc (الزيادة/النقصان)، $mul (الضرب)، $rename (إعادة التسمية)، $min/$max (التحديثات الشرطية)، و$currentDate (الوقت الحالي)، و$setOnInsert (التعيين فقط أثناء عملية upsert).

كيفية العمل: يتم تطبيق مُعدِّلات التحديث بشكل متكامل على مستوى المستند — أي أن تأثيرات جميع المُعدِّلات إما تُطبق بالكامل أو لا تُطبق على الإطلاق. يمكن استخدام عدة مُعدِّلات معًا (على سبيل المثال، $set + $inc + $currentDate)، ولكن لا يمكن تطبيق عدة مُعدِّلات على الحقل نفسه.

100%
graph TB
    A[Update Modifier] --> B[Field Value Class<br/>$set/$unset/$inc/$mul]
    A --> C[Field Name Class<br/>$rename]
    A --> D[Conditional Update Class<br/>$min/$max]
    A --> E[Time-Related<br/>$currentDate]
    A --> F[upsertDedicated<br/>$setOnInsert]

    style A fill:#cce5ff
المُعدِّل الوظيفة مثال هل ينشئ حقلًا؟
$set تعيين قيمة الحقل { $set: { price: 699 } } إنشاء الحقل إذا لم يكن موجودًا
$unset حذف الحقل { $unset: { discount: "" } } تجاهل إذا كان الحقل غير موجود
$inc زيادة/تقليل القيمة { $inc: { stock: -1 } } البدء من 0 في حالة عدم وجود الحقل
$mul الضرب { $mul: { price: 0.9 } } البدء من 0 في حالة عدم وجود الحقل
$rename إعادة تسمية الحقل { $rename: { "stock": "qty" } }
$min اختيار القيمة الأصغر { $min: { price: 500 } } إنشاء الحقل إذا لم يكن موجودًا
$max اختيار القيمة الأكبر { $max: { price: 1000 } } إنشاء الحقل إذا لم يكن موجودًا
$currentDate تعيين الوقت الحالي { $currentDate: { updatedAt: true } } إنشاء الحقل إذا لم يكن موجودًا
$setOnInsert الإعداد لـ «upsert» فقط { $setOnInsert: { createdAt: new Date() } } الإنشاء عند الإدراج فقط

(1) $set تعيين قيمة الحقل

JAVASCRIPT
// === Set Fields ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { stock: 50, isActive: true } }
);

// === Set Up Nested Fields ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { "specs.battery": "5000mAh" } }
);

// === Setting Array Elements ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { "tags.0": "5g", "tags.1": "amoled" } }
);

(2) $unset: يحذف حقلًا

JAVASCRIPT
// === Delete a Single Field ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $unset: { discount: "" } }
);

// === Delete Multiple Fields ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $unset: { discount: "", internalNotes: "" } }
);

// === Delete Nested Fields ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $unset: { "specs.battery": "" } }
);

(3) الزيادة $inc

JAVASCRIPT
// === Inventory Write-Downs ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $inc: { stock: -1 } }
);

// === Page Views +1 ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $inc: { viewCount: 1 } }
);

// === Cumulative Score(Multiple fields)===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $inc: { stock: -1, soldCount: 1, viewCount: 1 } }
);

(4) الضرب باستخدام $mul

JAVASCRIPT
// === Apply 10% off ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $mul: { price: 0.9 } }
);

// === Prices Have Doubled ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $mul: { price: 2 } }
);

(5) $rename: إعادة تسمية حقل

JAVASCRIPT
// === Rename Field ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $rename: { "stock": "inventory" } }
);
// stock → inventory

// === Renaming Nested Fields ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $rename: { "specs.battery": "specs.batteryCapacity" } }
);

(6) $min / $max: أخذ القيمة الدنيا/القيمة القصوى

JAVASCRIPT
// === $min:Update only during on-duty hours ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $min: { price: 500 } }
);
// If the current price > 500,Change to 500;Otherwise, no change

// === $max:Update only when the value is greater ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $max: { price: 1000 } }
);

(7) تحدد $currentDate التاريخ الحالي

JAVASCRIPT
// === Set the Current Time ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $currentDate: { lastModified: true } }
);

// === Set to Date Type ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $currentDate: { lastModified: { $type: "date" } } }
);

(8) $setOnInsert: تعيين قيمة لحقل ما أثناء عملية «upsert»

JAVASCRIPT
// === Only at upsert Set a default value upon insertion ===
db.products.updateOne(
  { sku: 'NEW-001' },
  {
    $set: { price: 599 },
    $setOnInsert: { createdAt: new Date(), stock: 0 }
  },
  { upsert: true }
);
// If inserted:{ sku: 'NEW-001', price: 599, createdAt: ..., stock: 0 }
// If updated:{ sku: 'NEW-001', price: 599 }(Do not set createdAt、stock)

▶ المثال 3: دليل عملي لتحديث الحقول المركبة

JAVASCRIPT
// === Update the order status after the payment is successful ===
db.orders.updateOne(
  { _id: orderId },
  {
    $set: {
      status: 'paid',
      paidAt: new Date(),
      paymentMethod: 'credit_card'
    },
    $inc: { version: 1 },         // Optimistic Lock Version Number
    $currentDate: { updatedAt: true }
  }
);

// === Update the last login time after the user logs in ===
db.users.updateOne(
  { _id: userId },
  {
    $set: { lastLoginAt: new Date(), lastLoginIp: '192.168.1.1' },
    $inc: { loginCount: 1 }
  }
);

الإخراج:

TEXT 📖 للعرض فقط
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }


7. مُعدِّلات تحديث المصفوفات

شرح المفهوم: تعد المصفوفات أكثر هياكل البيانات مرونةً في مستندات MongoDB، لكن تحديث عناصر المصفوفة أكثر تعقيدًا من تحديث الحقول العادية. توفر MongoDB مُعدِّلات مخصصة للمصفوفات— $push (إضافة عنصر)، و$pull (حذف العناصر المطابقة)، و$addToSet (الإضافة دون تكرار)، و$pop (حذف العنصر الأول والأخير)، بالإضافة إلى عوامل تحديد الموضع $ و$[] لتحديث عناصر محددة في المصفوفة بدقة.

كيفية العمل: تعمل مُعدِّلات المصفوفات على عناصر المصفوفة نفسها، وليس على المستند بأكمله. والفرق الرئيسي بين $push و$addToSet هو أن $push تضيف العناصر دون قيد أو شرط (مما قد يؤدي إلى تكرار العناصر)، بينما تتحقق $addToSet أولاً من وجود العنصر بالفعل (لإزالة التكرارات). يعمل عامل تحديد الموضع $، عند استخدامه مع شرط تصفية، على تحديد موقع «أول عنصر مطابق في المصفوفة»؛ بينما يعمل $[] على «جميع عناصر المصفوفة»؛ ويقوم $[identifier] + arrayFilters بإجراء «تحديثات مجمعة بناءً على الشروط».

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

100%
graph TB
    A[Array Update Modifiers] --> B[Add an element<br/>$push / $addToSet]
    A --> C[Delete Element<br/>$pull / $pop]
    A --> D[Bulk Operations<br/>$each / $slice]
    A --> E[Location Update<br/>$ / $[] / $[filter]]

    style A fill:#cce5ff
المُعدِّل الوظيفة إزالة التكرارات مثال تكرار الاستخدام
$push إضافة عنصر { $push: { tags: "new" } } ⭐⭐⭐
$addToSet إزالة التكرارات والإضافات { $addToSet: { tags: "new" } } ⭐⭐
$pull حذف العناصر المتطابقة { $pull: { tags: "old" } } ⭐⭐
$pop إزالة العنصر الأول/الأخير { $pop: { tags: 1 } }
$each الإضافة الجماعية (بالاقتران مع $push) { $push: { tags: { $each: [...] } } } ⭐⭐
$slice الحد الأقصى لطول المصفوفة { $push: { tags: { $each: [...], $slice: -5 } } } ⭐⭐
$position تحديد موضع الإدراج { $push: { tags: { $each: [...], $position: 0 } } }

(1) $push: إضافة عنصر إلى مصفوفة

JAVASCRIPT
// === Add a Single Element ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $push: { tags: 'bestseller' } }
);

// === Add Multiple Elements($each)===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $push: { tags: { $each: ['5g', 'amoled', 'fast-charging'] } } }
);

// === Limit the array size($slice + $position)===
db.products.updateOne(
  { sku: 'PHONE-001' },
  {
    $push: {
      tags: {
        $each: ['new1', 'new2', 'new3'],
        $slice: -5,            // Keep only the last one 5 items
        $position: 0           // Insert from the beginning
      }
    }
  }
);

(2) تحذف الأداة $pull العناصر المطابقة

JAVASCRIPT
// === Delete a Specified Value ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $pull: { tags: 'old-tag' } }
);

// === Delete all elements that meet the criteria ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $pull: { tags: { $in: ['outdated1', 'outdated2'] } } }
);

(3) $addToSet: إضافة عناصر إلى مصفوفة مع إزالة التكرارات

JAVASCRIPT
// === Add only elements that do not exist ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $addToSet: { tags: 'new-tag' } }
);
// If tags Included 'new-tag',Do not add again

// === Add multiple($each)===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $addToSet: { tags: { $each: ['tag1', 'tag2'] } } }
);

(4) تزيل الدالة $pop العنصر الأول أو الأخير

JAVASCRIPT
// === Delete the last element ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $pop: { tags: 1 } }
);

// === Delete the first element ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $pop: { tags: -1 } }
);

(5) تحديد مواقع عناصر المصفوفة وتحديثها

JAVASCRIPT
// === Update via Position Index ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { "tags.0": "updated-first-tag" } }
);

// === Through $ Positioning Symbol Update(The first matching element found)===
db.products.updateOne(
  { sku: 'PHONE-001', "reviews.userId": 'user_001' },
  { $set: { "reviews.$.helpful": 10 } }
);
// Found userId='user_001' Comments on,Set it to helpful Field

// === Batch Update Array Elements ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { "reviews.$[].status": "approved" } }
);
// All Comments status → approved

(6) $[] تحديث جميع العناصر

JAVASCRIPT
// === Update all array elements ===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { "reviews.$[].status": "approved" } }
);

// === Updating Array Elements Based on Conditions(arrayFilters)===
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { "reviews.$[lowRating].flagged": true } },
  {
    arrayFilters: [{ "lowRating.rating": { $lt: 2 } }]
  }
);
// Rating by tags only < 2 Comments on
}
);

الإخراج:

TEXT 📖 للعرض فقط
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }

▶ المثال 4: عمليات تحديث المصفوفات العملية

JAVASCRIPT
// === Scene:E-commerce Review System ===
// 1. Add a comment
db.products.updateOne(
  { sku: 'PHONE-001' },
  {
    $push: {
      reviews: {
        userId: 'user_001',
        rating: 5,
        content: 'Excellent phone!',
        createdAt: new Date(),
        helpful: 0
      }
    }
  }
);

// 2. Delete a specific comment from a user
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $pull: { reviews: { userId: 'user_001' } } }
);

// 3. Flag low-rated reviews
db.products.updateOne(
  { sku: 'PHONE-001' },
  { $set: { "reviews.$[r].flagged": true } },
  { arrayFilters: [{ "r.rating": { $lt: 2 } }] }
);

// 4. Limit the maximum number of comments 100 items
db.products.updateOne(
  { sku: 'PHONE-001' },
  {
    $push: {
      reviews: {
        $each: [newReview],
        $slice: -100
      }
    }
  }
);


8. الخيار upsert

شرح المفهوم: UPSERT = UPDATE + INSERT. وهو وضع كتابة فريد في MongoDB — إذا كان المستند موجودًا، يتم تحديثه؛ وإذا لم يكن موجودًا، يتم إدراجه. ويُعد هذا الوضع حاسمًا في سيناريوهات «الكتابة المتكررة» (idempotent write): حيث تظل النتيجة متسقة بغض النظر عن عدد مرات تنفيذ العملية. تشمل السيناريوهات النموذجية: سجلات تسجيل دخول المستخدم (يتم إنشاؤها عند أول تسجيل دخول كل يوم، مع تحديثات لاحقة)؛ وعربات التسوق (يتم إنشاؤها عند إضافة أول عنصر، مع تحديثات لاحقة للكمية)؛ وإعدادات التكوين (يتم إنشاؤها عند الإعداد الأولي، مع تعديلات لاحقة على القيم).

كيفية العمل: عند تعيين upsert: true، يحاول MongoDB أولاً مطابقة المستندات باستخدام عامل التصفية. إذا تم العثور على مستند مطابق، فإنه يطبق مُعدِّل التحديث (تمامًا مثل updateOne العادي). وإذا لم يتم العثور على مستند مطابق، فإنه يدمج شروط المساواة في عامل التصفية مع $set/$setOnInsert من مُعدِّل التحديث لإدراج مستند جديد. لا يسري مفعول $setOnInsert إلا أثناء الإدراج ويتم تجاهله أثناء عمليات التحديث — وهذه هي أفضل طريقة لتعيين القيم الافتراضية.

100%
graph TB
    A[updateOne + upsert: true] --> B{filter Matching Documents?}
    B -->|Yes| C[Apply $set update modifier]
    B -->|No| D[Merge filter conditions + $set + $setOnInsert]
    D --> E[Insert a New Document]
    C --> F[Back matchedCount=1<br/>upsertedCount=0]
    E --> G[Back matchedCount=0<br/>upsertedCount=1<br/>upsertedId=ObjectId]

    style C fill:#d4edda
    style E fill:#fff3cd
سلوك الإدراج أو التحديث عدد المطابقات عدد التعديلات عدد عمليات الإدراج أو التحديث معرّف عملية الإدراج أو التحديث
البحث والتعديل 1 0 أو 1 0 فارغ
لم يتم العثور عليه، أدخله 0 0 1 ObjectId(...)
لم يتم العثور عليه، لا يوجد «upsert» 0 0 0 null

(1) ما المقصود بـ «upsert»؟

upsert = التحديث + الإدراج؛ إذا كان السجل موجودًا، فقم بتحديثه؛ وإذا لم يكن موجودًا، فقم بإدراجه.

100%
graph TB
    A[updateOne + upsert] --> B{The document exists?}
    B -->|Yes| C[Execute $set update]
    B -->|No| D[Insert a New Document<br/>Apply $set + filter fields]

    style C fill:#d4edda
    style D fill:#fff3cd

(2) سلوك «Upsert»

JAVASCRIPT
// === upsert: false(Default)===
const result1 = await Product.updateOne(
  { sku: 'NEW-001' },
  { $set: { price: 599 } }
);
print(result1.matchedCount);   // 0(No matches found)
print(result1.modifiedCount);  // 0
print(result1.upsertedCount);  // 0

// === upsert: true ===
const result2 = await Product.updateOne(
  { sku: 'NEW-001' },
  { $set: { price: 599 } },
  { upsert: true }
);
print(result2.upsertedCount);  // 1(Insert 1 items)
print(result2.upsertedId);     // ObjectId('...')

(3) لا يتم تعيين $setOnInsert إلا عند الإدراج

JAVASCRIPT
// === Complete upsert Pattern ===
db.products.updateOne(
  { sku: 'NEW-001' },
  {
    $set: { price: 599, updatedAt: new Date() },
    $setOnInsert: { createdAt: new Date(), stock: 0, viewCount: 0 }
  },
  { upsert: true }
);

// === Insert if not present:{ sku: 'NEW-001', price: 599, updatedAt: ..., createdAt: ..., stock: 0, viewCount: 0 }
// === Update if it exists:{ sku: 'NEW-001', price: 599, updatedAt: ..., createdAt: <Old value> }

▶ المثال 5: دليل عملي لـ UPSERT

JAVASCRIPT
// === User Login History upsert ===
db.user_logins.updateOne(
  {
    userId: 'user_001',
    date: '2026-07-01'
  },
  {
    $set: { lastLoginAt: new Date() },
    $inc: { loginCount: 1 },
    $setOnInsert: { firstLoginAt: new Date() }
  },
  { upsert: true }
);
// Create a record for the first login of each day,Future Updates

// === Shopping Cart upsert ===
db.carts.updateOne(
  { userId: 'user_001' },
  {
    $set: { updatedAt: new Date() },
    $inc: { totalItems: 2 }
  },
  { upsert: true }
);

الإخراج:

TEXT 📖 للعرض فقط
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }
{ acknowledged: true, matchedCount: 1, modifiedCount: 1 }


9. أفضل الممارسات لعمليات التحديث

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

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

100%
graph TB
    A[Best Practices for Updates] --> B[Atomicity<br/>filter + Atomic Modifiers]
    A --> C[Performance<br/>bulkWrite + Index]
    A --> D[Safety<br/>Return Value Checking + Version Control]

    B --> B1[✅ Recommendations: filter Conditional Filtering<br/>{ sku, stock: { $gt: 0 } }]
    B --> B2[❌ Avoid: Check First, Then Edit<br/>findOne + updateOne]

    style B1 fill:#d4edda
    style B2 fill:#f8d7da
الممارسات أفضل الممارسات الأنماط السيئة
أمان التزامن filter + العمليات الذرية findOne + updateOne
التحديث الجماعي bulkWrite + ordered: false Loop updateOne
التحكم في الإصدارات $inc: { __v: 1 } لا يوجد رقم إصدار
معالجة الأخطاء التحقق من قيم matchedCount و modifiedCount تجاهل قيمة الإرجاع

(1) ضمان الترابطية

JAVASCRIPT
// ✅ Safety:filter + Atomic Manipulation
const result = await Product.updateOne(
  { sku: 'PHONE-001', stock: { $gt: 0 } },
  { $inc: { stock: -1 } }
);

// ❌ Unsafe:Check First, Then Edit(Competitive Conditions)
const product = await Product.findOne({ sku: 'PHONE-001' });
if (product.stock > 0) {
  await Product.updateOne(
    { sku: 'PHONE-001' },
    { $inc: { stock: -1 } }
  );
}

(2) تحسين الأداء

نظرة عامة على المفهوم: يتمحور تحسين أداء عمليات التحديث حول ثلاث استراتيجيات أساسية: تقليل عدد جولات الذهاب والإياب عبر الشبكة (باستخدام bulkWrite بدلاً من الاستدعاءات المتكررة لـ updateOne)، والتصفية حسب حقول الفهرس (لتجنب المسح الكامل للجدول)، وتجنب إعادة كتابة المستندات غير الضرورية (تحديث الحقول التي تغيرت فقط). ومن بين هذه الاستراتيجيات، توفر bulkWrite أكبر تحسن في الأداء — ففي حين تستغرق 100 عملية updateOne منفصلة حوالي 10 ثوانٍ، لا تستغرق عملية bulkWrite واحدة سوى 0.1 ثانية.

كيفية العمل: يتطلب كل استدعاء لـ updateOne رحلة ذهاب وإياب كاملة عبر الشبكة — حيث يرسل العميل طلبًا → يقوم الخادم بمطابقة المستند → يتم تحديث التطبيق → يتم إرجاع النتيجة. أما bulkWrite فيجمع أكثر من 100 عملية في طلب شبكي واحد؛ حيث ينفذ الخادم جميع العمليات بالترتيب ويعيد النتائج في دفعة واحدة. بالإضافة إلى ذلك، يستخدم محرك التخزين WiredTiger آلية MVCC عند تحديث المستندات — إذا زاد حجم المستند بعد التحديث ولم تكن المساحة كافية في الموقع الأصلي، يتم نقل المستند إلى موقع جديد، مما يؤدي إلى تحديث جميع إدخالات الفهرس. لذلك، فإن تقليل التغييرات في حجم المستند (مثل استبدال $inc بـ $set، مما يؤدي إلى إعادة كتابة الحقل الرقمي بالكامل) يؤدي أيضًا إلى تحسين الأداء.

استراتيجية التحسين تحسين الأداء تغييرات في الكود مستوى التوصية
bulkWrite بديل لحلقة updateOne 10–100x متوسط ⭐⭐⭐
تصفية حقول الفهرس 10–1,000x منخفضة ⭐⭐⭐
$inc استبدال وإعادة كتابة الحقول الرقمية 1.5–2x منخفضة ⭐⭐
التحكم في حجم الدفعة (1,000/دفعة) 1.5–3x منخفض ⭐⭐
التغير في حجم المستند 1.2–1.5x منخفض
JAVASCRIPT
// === Optimization 1:Batch updates instead of multiple individual updates ===
// ❌ Slow: 100 times updateOne
for (const item of items) {
  await Product.updateOne({ sku: item.sku }, { $inc: { stock: -item.qty } });
}

// ✅ Fast: 1 times bulkWrite
await Product.bulkWrite(
  items.map(item => ({
    updateOne: {
      filter: { sku: item.sku, stock: { $gte: item.qty } },
      update: { $inc: { stock: -item.qty, soldCount: item.qty } }
    }
  })),
  { ordered: false }
);

// === Optimization 2:Filter by Index Field ===
// ✅ Indexed:db.products.updateOne({ sku: 'PHONE-001' }, ...)
// ⚠️ No index:db.products.updateOne({ title: 'Phone' }, ...)

(3) معالجة الأخطاء

JAVASCRIPT
// === UpdateResult Processing ===
async function updateProductStock(sku, qty) {
  const result = await Product.updateOne(
    { sku, stock: { $gte: qty } },
    { $inc: { stock: -qty, soldCount: qty } }
  );

  if (result.matchedCount === 0) {
    throw new Error(`Out of stock or item not available: ${sku}`);
  }

  if (result.modifiedCount === 0) {
    throw new Error('Update Failed');
  }

  return result;
}


❓ أسئلة شائعة

س ما مدى الفرق في الأداء بين updateOne وupdateMany؟
ج يقوم updateMany بتحديث مستندات متعددة في عملية واحدة، وهو أمر فعال، لكنه يؤدي إلى قفل عدد أكبر من المستندات. بالنسبة للتحديثات المجمعة، نوصي باستخدام bulkWrite مع ordered: false، وهو أكثر مرونة من updateMany.
س هل يُصدر $set خطأً في حالة عدم وجود حقل ما؟
ج لا. يقوم $set بإنشاء الحقول تلقائيًا (بما في ذلك الحقول المتداخلة). كما أن استدعاء $unset على حقل غير موجود لا يسبب أي مشكلة.
س كيف يتم إنشاء _id أثناء عملية UPSERT؟
ج يتم استخدام قيمة حقل _id المُعلن في عامل التصفية تلقائيًا؛ وإذا لم يتضمن عامل التصفية حقل _id، فإن MongoDB تقوم تلقائيًا بإنشاء ObjectId.
س ماذا أفعل إذا حدث فقدان للدقة مع الأعداد العائمة من النوع $inc؟
ج قد تواجه الأعداد العائمة من النوع $inc مشكلات في الدقة. نوصي باستخدام النوع Decimal128 (mongoose.Types.Decimal128) لإجراء حسابات دقيقة.
س كيف يمكنني تحديد موقع العناصر عند تحديث حقول المصفوفة؟
ج استخدم أداة تحديد الموقع $ (للعثور على أول عنصر مطابق) أو $[identifier] + arrayFilters (لتحديث عناصر متعددة بناءً على شروط معينة).
س متى يجب عليّ استخدام replaceOne بدلاً من updateOne؟
ج استخدم updateOne + $set إذا كنت ترغب في تعديل بضعة حقول فقط؛ واستخدم replaceOne لإعادة كتابة المستند بأكمله. لا يحافظ replaceOne على الحقول غير المحددة، لذا استخدمه بحذر.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): استخدم updateOne لتعديل سعر المنتج، والمخزون، ووقت آخر تحديث.
  2. تمرين أساسي (⭐): استخدم $push لإضافة 3 علامات إلى منتج، واستخدم $addToSet لاختبار إزالة التكرارات.
  3. تمرين متقدم (⭐⭐): استخدم bulkWrite لتنفيذ عملية خصم المخزون من الطلب (الخصم الفردي لعدة أصناف) والتعامل مع الحالات التي يكون فيها المخزون غير كافٍ.
  4. مشكلة متقدمة (⭐⭐): استخدم upsert لتنفيذ إحصائيات تسجيل دخول المستخدم اليومية (قم بإنشائها عند أول ظهور، ثم قم بزيادتها عند الظهورات اللاحقة).
  5. التحدي (⭐⭐⭐): قم بتنفيذ ميزة دمج سلة التسوق لدمج العناصر الموجودة في سلة التسوق المؤقتة مع سلة التسوق الخاصة بالمستخدم، مع معالجة العناصر المكررة (عن طريق تجميع الكميات).
Web-Tutorial.com

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

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

100%