使用 $set 和 $unset 的 updateOne 与 updateMany
您将修改一个或多个文档中的指定字段,而无需替换整个文档。
使用 $set 和 $unset 的 updateOne 与 updateMany 是 CoddyKit 上的免费 MongoDB Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 MongoDB Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 MongoDB Academy 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
The Two Main Update Methods
MongoDB provides two primary methods for modifying existing documents:
- updateOne(filter, update, options) — modifies the first document matching the filter. If multiple documents match, only one is updated.
- updateMany(filter, update, options) — modifies all documents matching the filter.
Both methods return a result object with matchedCount (how many documents matched the filter) and modifiedCount (how many were actually changed—some matching documents may already have the target value). Always verify modifiedCount in your application logic.
// updateOne: modify a specific document
const result = await db.collection('users').updateOne(
{ email: 'alice@example.com' }, // filter
{ $set: { lastLoginAt: new Date() } } // update
);
console.log(result.matchedCount); // 1
console.log(result.modifiedCount); // 1
// updateMany: modify all matching documents
const bulkResult = await db.collection('users').updateMany(
{ emailVerified: false },
{ $set: { needsVerification: true } }
);$set: Modifying Specific Fields
The $set operator sets the value of one or more fields. If a field does not exist, $set creates it. If it already exists, $set replaces its value. Only the specified fields are changed—all other fields in the document remain untouched.
This is the most important update operator and the one you will use in the vast majority of update operations. Never pass a plain document as the update argument without an operator—doing so replaces the entire document, losing all other fields.
// $set updates only the specified fields
db.users.updateOne(
{ _id: userId },
{ $set: { name: 'Alice Smith', updatedAt: new Date() } }
);
// Only 'name' and 'updatedAt' change; all other fields are preserved
// $set creates the field if it doesn't exist:
db.users.updateOne(
{ _id: userId },
{ $set: { newsletterOptIn: true } } // Added to doc even if it wasn't there
);
// $set on a nested field:
db.users.updateOne(
{ _id: userId },
{ $set: { 'address.city': 'New York' } } // Dot notation for nestedThe Whole-Document Replacement Trap
A critical MongoDB beginner mistake: if you pass a plain document as the update argument—without any update operators—MongoDB treats it as a full document replacement. The matched document is replaced entirely with the new document (preserving only _id). All other fields are lost.
This behavior is actually intentional and useful for replacing an entire document, but it is a common source of accidental data loss. Always use update operators ($set, $inc, etc.) unless you explicitly intend to replace the full document.
// DANGER: This REPLACES the entire document!
db.users.updateOne(
{ _id: userId },
{ name: 'Alice Smith' } // No $set operator!
);
// Before: { _id: ..., name: 'Alice', email: 'alice@...', age: 28 }
// After: { _id: ..., name: 'Alice Smith' } <-- email and age are GONE!
// CORRECT: Use $set to modify only name
db.users.updateOne(
{ _id: userId },
{ $set: { name: 'Alice Smith' } } // Only name changes
);$unset: Removing Fields
The $unset operator removes one or more fields from a document. The value you assign in $unset is ignored—conventionally set to '' or 1. The field is completely removed from the document, not set to null.
Use $unset when you want to clean up legacy fields during a schema migration, remove sensitive data (like a password reset token after it is used), or strip fields that should no longer be stored on certain documents.
// Remove specific fields from a document
db.users.updateOne(
{ _id: userId },
{ $unset: { passwordResetToken: '', passwordResetExpiry: '' } }
);
// Both fields are completely removed from the document
// $unset during schema migration: remove legacy field from all docs
db.users.updateMany(
{ legacyField: { $exists: true } },
{ $unset: { legacyField: '' } }
);
// Removes 'legacyField' from every document that has itCombining $set and $unset
You can combine multiple update operators in a single updateOne or updateMany call. They are applied atomically in a single operation—there is no window where the document is in a partially updated state.
This is important for schema migrations where you simultaneously add a new field and remove an old one. Doing it in one atomic operation prevents any client from reading a document in the transitional state where both old and new fields exist.
// Atomic schema migration: rename 'displayName' to 'username'
// (set new field, copy value from old field, remove old field)
db.users.updateMany(
{ displayName: { $exists: true } },
{
$rename: { displayName: 'username' } // Atomic rename
// $rename is equivalent to $set + $unset in one operator
}
);
// Or manually with $set + $unset + $currentDate:
db.orders.updateOne(
{ _id: orderId },
{
$set: { status: 'shipped', trackingNumber: 'UPS123' },
$unset: { processingNote: '' },
$currentDate: { shippedAt: true }
}
);$rename: Renaming Fields
The $rename operator atomically renames a field within a document. It is equivalent to $seting the new name with the old value and then $unseting the old name—but done in a single atomic step.
Use $rename when refactoring your schema and changing field names. It works correctly even when the destination field does not exist. If the source field does not exist, $rename does nothing (no error). It cannot rename fields across embedded documents.
// Rename 'fname' and 'lname' to 'firstName' and 'lastName'
db.users.updateMany(
{}, // All documents
{ $rename: { fname: 'firstName', lname: 'lastName' } }
);
// Before: { _id: ..., fname: 'Alice', lname: 'Smith' }
// After: { _id: ..., firstName: 'Alice', lastName: 'Smith' }
// If source field doesn't exist, $rename silently skips
// No error thrown$currentDate: Setting Timestamps
The $currentDate operator sets a field to the current date/time at the moment the update is executed on the server. Using server-side timestamps is more reliable than passing a date from the application, because it avoids clock skew between multiple application servers.
Set the value to true to use the BSON Date type, or { $type: 'timestamp' } for the BSON Timestamp type (used internally by the replication system). In application code, BSON Date is almost always what you want.
// Set multiple timestamp fields atomically
db.orders.updateOne(
{ _id: orderId, status: 'pending' },
{
$set: { status: 'processing' },
$currentDate: {
processingStartedAt: true, // BSON Date
lastModified: true
}
}
);
// processingStartedAt = server-side current time
// More reliable than Date.now() from applicationThe upsert Option
Setting { upsert: true } in the options tells MongoDB: if no document matches the filter, insert a new document that combines the filter fields and the update. This is the upsert (update-or-insert) pattern—useful for 'insert if not exists, update if exists' semantics without a separate read operation.
The inserted document contains the fields from both the filter and the $set. Check result.upsertedId to see if an insert occurred. Upserts are atomic—no race condition between checking existence and inserting.
// Upsert: update if exists, insert if not
const result = await db.collection('userPreferences').updateOne(
{ userId: userId }, // filter
{
$set: { theme: 'dark', language: 'en' }, // update
$setOnInsert: { createdAt: new Date() } // only on insert
},
{ upsert: true } // option
);
if (result.upsertedId) {
console.log('New preferences created:', result.upsertedId);
} else {
console.log('Existing preferences updated');
}updateMany: Bulk Field Changes
updateMany applies the same update to all matching documents in one operation. This is how you perform bulk schema migrations, add default values to missing fields, or update a batch of records based on a condition.
Be careful with updateMany({}, ...) (empty filter)—it modifies every document in the collection. Always include a filter that limits the scope. Consider adding a condition like { newField: { $exists: false } } to only process documents that need the change.
// Add default value to all existing docs missing the field
await db.collection('products').updateMany(
{ featured: { $exists: false } }, // Only docs without the field
{ $set: { featured: false } } // Set default
);
// Batch status update
await db.collection('orders').updateMany(
{ status: 'processing', createdAt: { $lt: new Date(Date.now() - 86400000 * 7) } },
{ $set: { status: 'stalled', stalledAt: new Date() } }
);
// Flag orders processing for more than 7 daysfindOneAndUpdate: Atomic Read-Modify
findOneAndUpdate(filter, update, options) atomically finds a document, updates it, and returns it in a single operation. This eliminates the race condition that exists when you do a separate findOne followed by an updateOne.
By default it returns the document before the update. Set { returnDocument: 'after' } to get the updated version. Combine with upsert: true to find-or-create patterns. This is ideal for claiming queue items, implementing counters, and any pattern where you need to both modify and read the document atomically.
// Claim the next pending job atomically
const job = await db.collection('jobs').findOneAndUpdate(
{ status: 'pending' }, // filter
{
$set: { status: 'claimed', workerId: workerId, claimedAt: new Date() }
},
{
sort: { priority: -1, createdAt: 1 }, // Pick highest priority, oldest
returnDocument: 'after' // Return updated document
}
);
if (job) {
await processJob(job); // job is fully updated in DB already
}Update Operators Reference Summary
MongoDB provides many update operators beyond and . Here is a quick reference of the most important ones you will encounter:
- : Set field value (create if absent)
- : Remove a field completely
- : Rename a field atomically
- : Increment/decrement a numeric field
- : Multiply a numeric field
- /: Conditionally update to smaller/larger value
- /: Add/remove array elements
- : Add to array only if unique
- : Set to server-side current date/time
// Multiple operators in one atomic update
db.userStats.updateOne(
{ userId: userId },
{
: { lastSeen: new Date(), status: 'active' },
: { pageViews: 1, sessionCount: 1 },
: { longestSession: sessionDuration },
: { visitedPages: currentPage },
: { updatedAt: true }
},
{ upsert: true }
);
// All six operators apply atomically in one round tripQuick Check
Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.
Lesson Recap
In this lesson you learned: $set modifies specific fields without touching others—always use it rather than passing a bare document to avoid accidental full-document replacement, $unset removes fields entirely and is essential for schema migrations and cleaning up sensitive temporary data like password reset tokens, and upsert: true combined with $setOnInsert enables atomic find-or-create patterns without race conditions. Next up we explore numeric mutation operators: $inc, $mul, $min, and $max.
常见问题解答
「使用 $set 和 $unset 的 updateOne 与 updateMany」课时是免费的吗?
是的 — 「使用 $set 和 $unset 的 updateOne 与 updateMany」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 MongoDB Academy 课程的其余内容,请升级到 CoddyKit PRO。 MongoDB Academy 课程共包含 4 节课。
「使用 $set 和 $unset 的 updateOne 与 updateMany」这节课中我会学到什么?
您将修改一个或多个文档中的指定字段,而无需替换整个文档。 你通过在浏览器中直接运行的动手代码来练习 MongoDB Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 MongoDB Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 MongoDB Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「使用 $set 和 $unset 的 updateOne 与 updateMany」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 MongoDB Academy 课中编写并运行代码吗?
能。每节 MongoDB Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 $set 和 $unset 的 updateOne 与 updateMany
- 递增、乘法与最小值/最大值运算符
- 数组更新运算符:$push、$pull、$addToSet
- 使用 deleteOne 和 deleteMany 安全删除文档