Document Update: A Detailed Explanation of `updateOne` and `updateMany`

Updating documents is one of the most common write operations in MongoDB—mastering the update modifier is key to modifying data.

This course provides an in-depth exploration of updateOne, updateMany, and replaceOne, various update modifiers ($set, $inc, $push, $pull), upsert behavior, and atomicity guarantees.

1. What You'll Learn


2. A True Story About an E-commerce Platform

(1) Pain Point: Concurrency Issues When Deducting Inventory

Alice maintains the order system at an e-commerce company, where she encountered a classic concurrency issue while updating inventory:

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) Solutions for Atomic Updates in 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) Revenue

Dimension Lookup-then-update Atomic update
Concurrency Safety ❌ Race Conditions ✅ Atomic Operations
Performance ⚠️ Two queries ⚡ One operation
Code Complexity High Low

3. updateOne: Update a Single Document

Concept Explanation: updateOne is the most commonly used update method in MongoDB; it matches the first document based on filter conditions and applies the update operation. It is similar to SQL’s UPDATE ... SET ... WHERE ..., but MongoDB uses update modifiers (such as $set and $inc) to specify what to modify, rather than replacing the entire document. This design makes partial updates more efficient—only the changed fields are modified, rather than rewriting the entire document.

How It Works: updateOne The execution flow is as follows: Matching phase (locating documents based on the filter) → Update phase (applying update modifiers) → Index update (if index fields are modified) → Write Concern confirmation. The entire operation is atomic for a single document—there is no intermediate state where “only half of the fields are updated.”

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 }
Parameter Type Description
filter Document Search Criteria (Required)
update Document Update operation (required; must include a modifier)
options Document upsert / writeConcern etc.(optional)
Return Field Meaning Notes
matchedCount Number of matching documents May be 0
modifiedCount Actual number of documents modified 0 if the value is the same and has not changed
upsertedCount Number of documents inserted via upsert May be 1 only when upsert: true

(1) Basic Syntax

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

// Return Results:
// {
//   acknowledged: true,
//   matchedCount: 1,    // Number of matching documents
//   modifiedCount: 1,   // Number of documents modified
//   upsertedCount: 0,   // Number of inserted documents
//   upsertedId: null    // Inserted _id
// }

(2) Interpretation of Return Values

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)
Field Meaning
matchedCount Number of documents matching the filter
modifiedCount Actual number of documents modified
upsertedCount Number of documents inserted via upsert
upsertedId _id inserted into the document

(3) Handling of No Matches

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

▶ Example 1: put updateOne into practice

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() } }
);

4. updateMany: Batch Update

Concept Explanation: updateMany matches all documents that meet the criteria and applies the update operation uniformly to all of them. Unlike updateOne, which modifies only the first match, updateMany can update tens of thousands of documents at once. This is the core method for batch modifications (such as site-wide discounts, bulk delisting, and data repair).

How It Works: updateMany First, a query is executed to match all documents that meet the criteria, and then the update operation is applied to each document one by one. The update process is not transactional—if it fails midway, the documents that have already been updated will not be rolled back. Therefore, when performing bulk updates, you need to consider executing them in batches and handling errors.

Dimension updateOne updateMany
Match Range First Match All Matches
Bulk Operations ❌ Single ✅ Bulk
Transaction Rollback ❌ Not supported ❌ Not supported
Use Cases Individual Edits Batch Discounts, Delistings, and Restorations
Risk Low Moderate (significant impact from user error)

(1) Basic Syntax

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

// Return Results:
// {
//   acknowledged: true,
//   matchedCount: 250,     // Match 250 items
//   modifiedCount: 250,    // Edit 250 items
//   upsertedCount: 0
// }

(2) Notes on Batch Updates

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;
}

▶ Example 2: Hands-On Guide to Batch Updates

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' } }
);

5. replaceOne: Replace the entire document

Concept Explanation: The fundamental difference between replaceOne and updateOne is that updateOne updates fields specified in the modifier while preserving unspecified fields; replaceOne, on the other hand, completely replaces the document’s content, and unspecified fields will be deleted. This is one of the most dangerous operations in MongoDB, and misuse can result in data loss.

Use Cases: Use replaceOne only when you need to completely rewrite a document (e.g., for data migration or document format upgrades). In most cases, use updateOne + $set to modify only the necessary fields.

Dimension updateOne + $set replaceOne
Unspecified field ✅ Keep ❌ Delete
Atomicity ✅ Single-document atomicity ✅ Single-document atomicity
Use Cases Modifying select fields Rewriting the entire document
Risk Low High (field loss)

(1) Difference from 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) Use Cases for 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. Field Update Modifiers

Concept Explanation: Update modifiers are the core syntax of MongoDB update operations and define how document fields are modified. Unlike SQL’s SET field = value, MongoDB provides a rich set of modifiers—$set (set value), $unset (delete field), $inc (increment/decrement), $mul (multiplication), $rename (rename), $min/$max (conditional updates), $currentDate (current time), and $setOnInsert (set only during upsert).

How It Works: Update modifiers are applied atomically at the document level—the effects of all modifiers are either applied in full or not applied at all. Multiple modifiers can be used in combination (e.g., $set + $inc + $currentDate), but you cannot apply multiple modifiers to the same field.

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
Modifier Function Example Creates a field?
$set Set field value { $set: { price: 699 } } Create field if it does not exist
$unset Delete field { $unset: { discount: "" } } Ignore if field does not exist
$inc Increment/Decrement Value { $inc: { stock: -1 } } Start at 0 if the field does not exist
$mul Multiplication { $mul: { price: 0.9 } } Start at 0 if the field does not exist
$rename Rename Field { $rename: { "stock": "qty" } }
$min Take the smaller value { $min: { price: 500 } } Create the field if it does not exist
$max Take the larger value { $max: { price: 1000 } } Create the field if it does not exist
$currentDate Set the current time { $currentDate: { updatedAt: true } } Create if the field does not exist
$setOnInsert Set only for upsert { $setOnInsert: { createdAt: new Date() } } Create only on insert

(1) $set Set a field value

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: Deletes a field

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 Increment

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 Multiplication

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: Rename a field

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: Take the minimum/maximum value

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 sets the current date

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: Set a field during an 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)

▶ Example 3: Hands-On Guide to Updating Composite Fields

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 }
  }
);

7. Array Update Modifiers

Concept Explanation: Arrays are the most flexible data structures in MongoDB documents, but updating array elements is more complex than updating regular fields. MongoDB provides dedicated array modifiers—$push (add an element), $pull (delete matching elements), $addToSet (add without duplicates), $pop (delete the first and last elements), as well as positioning operators $ and $[] for precisely updating specific elements in an array.

How It Works: Array modifiers operate on the array elements themselves, not the entire document. The key difference between $push and $addToSet is that $push adds elements unconditionally (which may result in duplicates), while $addToSet first checks whether an element already exists (to remove duplicates). The positioning operator $, when used with a filter condition, locates the “first matching array element”; $[] operates on “all array elements”; and $[identifier] + arrayFilters perform “batch updates based on conditions.”

Design Philosophy: MongoDB encourages embedding small amounts of associated data within arrays (such as a list of product reviews or user tags), but arrays that are too large (exceeding several hundred elements) can impact query and update performance. For large amounts of associated data, it is recommended to use separate collections with references.

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
Modifier Function Duplicate Removal Example Frequency of Use
$push Add Element { $push: { tags: "new" } } ⭐⭐⭐
$addToSet Duplicate removal and addition { $addToSet: { tags: "new" } } ⭐⭐
$pull Delete matching elements { $pull: { tags: "old" } } ⭐⭐
$pop Remove first/last element { $pop: { tags: 1 } }
$each Add in bulk (in conjunction with $push) { $push: { tags: { $each: [...] } } } ⭐⭐
$slice Limit array length { $push: { tags: { $each: [...], $slice: -5 } } } ⭐⭐
$position Specify insertion position { $push: { tags: { $each: [...], $position: 0 } } }

(1) $push: Add an element to an array

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 deletes matching elements

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: Adding elements to an array while removing duplicates

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 removes the first or last element

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) Locating and Updating Array Elements

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) $[] Update all elements

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

▶ Example 4: Practical Array Updates

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. The upsert option

Concept Explanation: UPSERT = UPDATE + INSERT. It is a unique write mode in MongoDB—if the document exists, it is updated; if it does not exist, it is inserted. This mode is crucial in "idempotent write" scenarios: the result remains consistent regardless of how many times the operation is executed. Typical scenarios include: user login records (created upon the first login each day, with subsequent updates); shopping carts (created upon the first item added, with subsequent updates to the quantity); and configuration settings (created upon initial setup, with subsequent value modifications).

How It Works: When upsert: true is set, MongoDB first attempts to match documents using the filter. If a matching document is found, it applies the update modifier (just like a regular updateOne). If no matching document is found, it combines the equality conditions in the filter with $set/$setOnInsert from the update modifier to insert a new document. $setOnInsert takes effect only during insertion and is ignored during updates—this is the best way to set default values.

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
upsert Behavior matchedCount modifiedCount upsertedCount upsertedId
Find and Modify 1 0 or 1 0 null
Not found, insert 0 0 1 ObjectId(...)
Not found, no upsert 0 0 0 null

(1) What is an upsert?

upsert = update + insert; if the record exists, update it; if it doesn't exist, insert it.

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 Behavior

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 is set only upon insertion

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> }

▶ Example 5: A Practical Guide to 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 }
);

9. Best Practices for Update Operations

Concept Explanation: Best practices for update operations revolve around three core principles: atomicity (avoiding race conditions), performance (reducing network round trips and lock hold times), and security (preventing accidental operations). Among these, atomicity is the most critical—MongoDB’s single-document operations are inherently atomic, but the “look-then-update” pattern undermines this guarantee of atomicity.

How It Works: MongoDB guarantees atomicity for single-document write operations—a updateOne operation either succeeds completely or fails completely; there is no intermediate state where the update is only partially completed. However, cross-document operations do not automatically provide atomicity (multi-document transactions require version 4.0 or later). Therefore, when designing a data model, you should try to place related data within the same document to take advantage of single-document atomicity.

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
Practice Best Practices Anti-Patterns
Concurrency Safety filter + atomic operations findOne + updateOne
Batch Update bulkWrite + ordered: false Loop updateOne
Version Control $inc: { __v: 1 } No version number
Error Handling Check matchedCount/modifiedCount Ignore return value

(1) Atomicity Guarantee

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) Performance Optimization

Concept Overview: Performance optimization for update operations revolves around three core strategies: reducing network round trips (using bulkWrite instead of repeated updateOne calls), filtering by index fields (to avoid full table scans), and avoiding unnecessary document rewrites (updating only the fields that have changed). Among these, bulkWrite delivers the most significant performance improvement—while 100 individual updateOne operations take approximately 10 seconds, a single bulkWrite operation takes only 0.1 seconds.

How It Works: Each call to updateOne involves a complete round trip over the network—the client sends a request → the server matches the document → the application is updated → the result is returned. bulkWrite combines 100+ operations into a single network request; the server executes all operations in order and returns the results in a single batch. Additionally, the WiredTiger storage engine uses the MVCC mechanism when updating documents—if the document size increases after the update and there is insufficient space at the original location, the document is moved to a new location, triggering updates to all index entries. Therefore, minimizing changes in document size (such as replacing $inc with $set, which rewrites the entire numeric field) also yields performance benefits.

Optimization Strategy Performance Improvement Code Changes Recommendation Level
bulkWrite Alternative to the updateOne loop 10–100x Medium ⭐⭐⭐
Index Field Filtering 10–1,000x Low ⭐⭐⭐
$inc Replace and rewrite numeric fields 1.5–2x Low ⭐⭐
Batch Size Control (1,000/batch) 1.5–3x Low ⭐⭐
Change in document size 1.2–1.5x Low
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) Error Handling

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;
}

❓ FAQ

Q How much of a performance difference is there between updateOne and updateMany?
A updateMany updates multiple documents in a single operation, which is efficient, but it locks more documents. For batch updates, we recommend using bulkWrite with ordered: false, which is more flexible than updateMany.
Q Does $set throw an error if a field doesn't exist?
A No. $set automatically creates fields (including nested fields). Calling $unset on a nonexistent field is also harmless.
Q How is the _id generated during an UPSERT operation?
A It automatically uses the value of the _id field declared in the filter; if the filter does not include _id, MongoDB automatically generates an ObjectId.
Q What should I do if there is a loss of precision with $inc floating-point numbers?
A $inc floating-point numbers can have precision issues. We recommend using the Decimal128 type (mongoose.Types.Decimal128) for precise calculations.
Q How do I locate elements when updating array fields?
A Use the $ locator (to find the first matching element) or $[identifier] + arrayFilters (to update multiple elements based on conditions).
Q When should I use replaceOne versus updateOne?
A Use updateOne + $set if you only want to modify a few fields; use replaceOne to rewrite the entire document. replaceOne does not preserve fields that are not specified, so use it with caution.

📖 Summary


📝 Exercises

  1. Basic Exercise (⭐): Use updateOne to modify the product price, inventory, and last updated time.
  2. Basic Exercise (⭐): Use $push to add 3 tags to a product, and use $addToSet to test for duplicate removal.
  3. Advanced Exercise (⭐⭐): Use bulkWrite to implement order inventory deduction (atomic deduction of multiple items) and handle scenarios where inventory is insufficient.
  4. Advanced Problem (⭐⭐): Use upsert to implement daily user login statistics (create on first occurrence, then increment for subsequent occurrences).
  5. Challenge (⭐⭐⭐): Implement a shopping cart merge feature to combine items from the temporary shopping cart into the user’s shopping cart, handling duplicate items (by aggregating quantities).
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏