MongoDB: تحديث المستند: شرح مفصل لـ `updateOne` و`updateMany`
آخر تحديث: 2026-08-26
يُعد تحديث المستندات إحدى عمليات الكتابة الأكثر شيوعًا في MongoDB — ويُعد إتقان استخدام المُعدِّل update أمرًا أساسيًّا لتعديل البيانات.
تقدم هذه الدورة استكشافًا متعمقًا لـ updateOne وupdateMany وreplaceOne، ومُعدِّلات التحديث المختلفة ($set و$inc و$push و$pull)، وسلوك upsert، وضمانات الترابطية.
1. ما ستتعلمه
- الاختلافات الأساسية بين updateOne و updateMany و replaceOne
- تحديثات الحقول: $set / $unset / $inc / $mul / $rename
- تحديثات المصفوفات باستخدام $push / $pull / $addToSet / $pop
- خيار «upsert» (الإدراج في حالة عدم وجود السجل)
- ضمان الترابطية التامة لعمليات التحديث
- تفسير قيمة الإرجاع (matchedCount / modifiedCount)
2. قصة حقيقية عن منصة للتجارة الإلكترونية
(1) المشكلة: مشكلات التزامن عند خصم المخزون
تتولى أليس إدارة نظام الطلبات في إحدى شركات التجارة الإلكترونية، حيث واجهت مشكلة تقليدية تتعلق بالتزامن أثناء تحديث المخزون:
// ❌ 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
// ✅ 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». وتُعتبر العملية برمتها عملية متكاملة بالنسبة للمستند الواحد — فلا توجد حالة وسيطة يتم فيها «تحديث نصف الحقول فقط».
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) قواعد النحو الأساسية
// === 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) تفسير قيم الإرجاع
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) التعامل مع حالات عدم وجود تطابق
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 عمليًّا
// === 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) قواعد النحو الأساسية
// === 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) ملاحظات حول التحديثات الجماعية
// ⚠️ 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: دليل عملي للتحديثات المجمعة
// === 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
// === 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
// ✅ 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)، ولكن لا يمكن تطبيق عدة مُعدِّلات على الحقل نفسه.
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 تعيين قيمة الحقل
// === 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: يحذف حقلًا
// === 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
// === 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
// === 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: إعادة تسمية حقل
// === 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: أخذ القيمة الدنيا/القيمة القصوى
// === $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 التاريخ الحالي
// === 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»
// === 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: دليل عملي لتحديث الحقول المركبة
// === 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 على تضمين كميات صغيرة من البيانات المرتبطة داخل المصفوفات (مثل قائمة تقييمات المنتجات أو علامات المستخدمين)، لكن المصفوفات الكبيرة جدًّا (التي تتجاوز عدة مئات من العناصر) قد تؤثر على أداء الاستعلامات وعمليات التحديث. وبالنسبة للكميات الكبيرة من البيانات المرتبطة، يُنصح باستخدام مجموعات منفصلة مع إشارات مرجعية.
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: إضافة عنصر إلى مصفوفة
// === 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 العناصر المطابقة
// === 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: إضافة عناصر إلى مصفوفة مع إزالة التكرارات
// === 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 العنصر الأول أو الأخير
// === 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) تحديد مواقع عناصر المصفوفة وتحديثها
// === 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) $[] تحديث جميع العناصر
// === 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: عمليات تحديث المصفوفات العملية
// === 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 إلا أثناء الإدراج ويتم تجاهله أثناء عمليات التحديث — وهذه هي أفضل طريقة لتعيين القيم الافتراضية.
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 = التحديث + الإدراج؛ إذا كان السجل موجودًا، فقم بتحديثه؛ وإذا لم يكن موجودًا، فقم بإدراجه.
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»
// === 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 إلا عند الإدراج
// === 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
// === 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 أو أحدث). لذلك، عند تصميم نموذج البيانات، يجب أن تحاول وضع البيانات ذات الصلة داخل المستند نفسه للاستفادة من الترابطية للمستند الواحد.
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) ضمان الترابطية
// ✅ 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 | منخفض | ⭐ |
// === 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) معالجة الأخطاء
// === 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 على حقل غير موجود لا يسبب أي مشكلة.$inc؟$inc مشكلات في الدقة. نوصي باستخدام النوع Decimal128 (mongoose.Types.Decimal128) لإجراء حسابات دقيقة.$ (للعثور على أول عنصر مطابق) أو $[identifier] + arrayFilters (لتحديث عناصر متعددة بناءً على شروط معينة).replaceOne بدلاً من updateOne؟updateOne + $set إذا كنت ترغب في تعديل بضعة حقول فقط؛ واستخدم replaceOne لإعادة كتابة المستند بأكمله. لا يحافظ replaceOne على الحقول غير المحددة، لذا استخدمه بحذر.📖 ملخص
- تقوم وظيفة
updateOneبتحديث مستند واحد؛ بينما تقوم وظيفةupdateManyبتحديث مستندات متعددة - replaceOne: يستبدل المستند بأكمله؛ وستُفقد الحقول التي لم يتم تحديدها
- مُعدِّلات الحقول: $set/$unset/$inc/$mul/$rename/$min/$max/$currentDate/$setOnInsert
- مُعدِّلات المصفوفات: $push/$pull/$addToSet/$pop/$each/$slice/$position
- الخيار
upsert: الإدراج في حالة عدم وجوده؛ أما$setOnInsertفيقوم بتعيين القيمة عند الإدراج فقط - العمليات الذرية: شرط التصفية + التحديث الذري، لتجنب حالات التنافس
- bulkWrite: الأداء الأمثل للتحديثات المجمعة
📝 تمارين
- تمرين أساسي (⭐): استخدم
updateOneلتعديل سعر المنتج، والمخزون، ووقت آخر تحديث. - تمرين أساسي (⭐): استخدم $push لإضافة 3 علامات إلى منتج، واستخدم $addToSet لاختبار إزالة التكرارات.
- تمرين متقدم (⭐⭐): استخدم
bulkWriteلتنفيذ عملية خصم المخزون من الطلب (الخصم الفردي لعدة أصناف) والتعامل مع الحالات التي يكون فيها المخزون غير كافٍ. - مشكلة متقدمة (⭐⭐): استخدم
upsertلتنفيذ إحصائيات تسجيل دخول المستخدم اليومية (قم بإنشائها عند أول ظهور، ثم قم بزيادتها عند الظهورات اللاحقة). - التحدي (⭐⭐⭐): قم بتنفيذ ميزة دمج سلة التسوق لدمج العناصر الموجودة في سلة التسوق المؤقتة مع سلة التسوق الخاصة بالمستخدم، مع معالجة العناصر المكررة (عن طريق تجميع الكميات).