0Pricing
MongoDB Academy · 课时

数组更新运算符:$push、$pull、$addToSet

您将使用针对数组的更新运算符,向文档内的数组添加或移除项目。

数组更新运算符:$push、$pull、$addToSet 是 CoddyKit 上的免费 MongoDB Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 MongoDB Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 MongoDB Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Arrays as First-Class Citizens

Arrays inside MongoDB documents are common—a product has tags, a user has roles, an order has items. When these arrays need to change (add a tag, remove a role, append an order item), you need array update operators that modify the array in-place rather than replacing the entire document.

MongoDB provides a full set of operators for this purpose: $push adds elements, $pull removes elements by value, $pop removes from ends, and $addToSet adds only if unique. All of these are atomic—the change happens in a single server-side operation.

$push: Adding Elements to an Array

The $push operator appends one or more elements to an array field. If the field does not exist, $push creates it as a new array containing the pushed element. If the field exists but is not an array, the operation fails.

A single $push with a plain value adds one element. To add multiple elements in one operation, combine $push with the $each modifier. $push does not check for duplicates—use $addToSet if you need uniqueness.

// Push a single element
db.posts.updateOne(
  { _id: postId },
  { $push: { tags: 'mongodb' } }
);
// tags: ['node', 'backend'] -> ['node', 'backend', 'mongodb']

// Push multiple elements using $each
db.posts.updateOne(
  { _id: postId },
  { $push: { tags: { $each: ['databases', 'nosql'] } } }
);
// tags: ['node'] -> ['node', 'databases', 'nosql']

// Push a sub-document
db.users.updateOne(
  { _id: userId },
  { $push: { addresses: { street: '123 Main St', city: 'Chicago' } } }
);

$push With $sort and $slice

Combining $push with the $sort and $slice modifiers creates a capped sorted array: push new elements, sort the array, then trim it to a maximum length. This pattern is perfect for maintaining a 'top N' list or a 'recent N items' log.

The $slice modifier limits the array to the first N elements after sorting. Use negative values for last N (e.g., $slice: -5 keeps the last 5 after sort). This combination requires $each even if you are pushing a single item.

// Maintain a 'top 5 scores' list
db.players.updateOne(
  { _id: playerId },
  {
    $push: {
      topScores: {
        $each:  [{ score: 9800, date: new Date() }],
        $sort:  { score: -1 },  // Sort by score descending
        $slice: 5               // Keep only top 5
      }
    }
  }
);
// After: topScores always has max 5 entries, highest scores first

$pull: Removing Elements by Value

The $pull operator removes all elements from an array that match a specified value or condition. Unlike $pop (which removes from an end), $pull removes elements by their value anywhere in the array.

You can pass a simple value (for primitive arrays) or a query condition (for arrays of sub-documents). $pull removes all matching elements in one atomic operation—there is no need to know the position of the element.

// Remove a tag by value
db.posts.updateOne(
  { _id: postId },
  { $pull: { tags: 'outdated' } }
);
// tags: ['mongodb', 'outdated', 'nosql'] -> ['mongodb', 'nosql']
// Removes ALL occurrences of 'outdated'

// Pull with a condition on array of sub-documents
db.orders.updateOne(
  { _id: orderId },
  { $pull: { items: { sku: 'REMOVED-ITEM' } } }
);
// Removes all items where sku = 'REMOVED-ITEM' from the items array

$pop: Removing From Array Ends

The $pop operator removes the first or last element of an array without needing to know the value. Use $pop: { field: 1 } to remove the last element, and $pop: { field: -1 } to remove the first element.

$pop is useful for implementing queues and deques: push to the back with $push, pop from the front with $pop: -1. However, for production queue systems, consider dedicated solutions—document-based queues have limitations under high concurrency.

// Remove the last element
db.playlists.updateOne(
  { _id: playlistId },
  { $pop: { songs: 1 } }   // 1 = last
);
// songs: ['A', 'B', 'C'] -> ['A', 'B']

// Remove the first element
db.queues.updateOne(
  { _id: queueId },
  { $pop: { items: -1 } }  // -1 = first
);
// items: ['task1', 'task2', 'task3'] -> ['task2', 'task3']

$addToSet: Unique Element Insertion

The $addToSet operator adds an element to an array only if it is not already present. If the element already exists, the operation is a no-op—no duplicate is added. This is ideal for maintaining sets of unique values like tags, user roles, or category memberships.

Like $push, $addToSet supports the $each modifier to add multiple unique elements in one operation. If the field does not exist, it creates the array. BSON type and value must match exactly for the duplicate check.

// Add tag only if not already present
db.posts.updateOne(
  { _id: postId },
  { $addToSet: { tags: 'mongodb' } }
);
// If tags already has 'mongodb': no change
// If not: 'mongodb' is added

// Add multiple unique roles with $each
db.users.updateOne(
  { _id: userId },
  { $addToSet: { roles: { $each: ['viewer', 'commenter'] } } }
);
// Only adds roles that aren't already in the array
// 'viewer' already exists? Skipped. 'commenter' new? Added.

$push vs $addToSet: Choosing

The choice between $push and $addToSet depends on whether order and duplicates are acceptable:

  • Use $push when: order matters, duplicates are acceptable (or expected), you need sorted arrays with $sort/$slice, or you are building a log/history where repeated values make sense
  • Use $addToSet when: you are managing a set of unique values (roles, permissions, tags, subscriptions) and adding an existing value should be a no-op

Keep in mind that $addToSet must scan the existing array to check for duplicates—for very large arrays (thousands of elements), this can be slow. Consider a separate collection for large unique sets.

// $push for ordered history (duplicates ok)
db.users.updateOne(
  { _id: userId },
  { $push: { loginHistory: { at: new Date(), ip: req.ip } } }
);
// Same IP can appear multiple times - that's fine for history

// $addToSet for unique roles (no duplicates)
db.users.updateOne(
  { _id: userId },
  { $addToSet: { roles: 'editor' } }
);
// 'editor' is either added or already there - always unique

Removing Specific Elements From Nested Arrays

When your document has an array of sub-documents and you need to remove a specific sub-document by one of its fields, use $pull with a condition expression. The condition is evaluated against each array element and all matching elements are removed.

For removing multiple specific values at once from a primitive array, $pullAll is a shortcut that accepts an array of values to remove: { $pullAll: { tags: ['oldTag1', 'oldTag2'] } }. It is equivalent to { $pull: { tags: { $in: ['oldTag1', 'oldTag2'] } } }.

// Remove all items in an order for a discontinued product
db.carts.updateOne(
  { _id: cartId },
  {
    $pull: {
      items: { productId: ObjectId('discontinued-id') }
    }
  }
);

// $pullAll: remove multiple values at once
db.posts.updateOne(
  { _id: postId },
  { $pullAll: { tags: ['spam', 'outdated', 'test'] } }
);
// Removes all occurrences of 'spam', 'outdated', and 'test'

Array Length Management

Be cautious about unbounded array growth. Arrays that grow without limit (unbounded arrays) cause documents to grow beyond MongoDB's 16 MB limit and degrade performance as the array index size grows.

Strategies for bounded arrays:

  • Use $push + $slice: -N to keep only the last N elements (rolling window)
  • Set a business rule: maximum 50 items in cart, maximum 100 history entries
  • For truly unbounded collections, move to a separate collection with a reference instead of embedding
// Keep only the last 100 login history entries
db.users.updateOne(
  { _id: userId },
  {
    $push: {
      loginHistory: {
        $each: [{ at: new Date(), ip: req.ip }],
        $slice: -100   // Keep last 100 (most recent)
      }
    }
  }
);
// loginHistory never exceeds 100 entries
// Oldest entries are automatically dropped

Real-World: User Following System

Social features like 'follow user' and 'unfollow user' map naturally to $addToSet and $pull. When a user follows someone, add the followed user's ID to the follower's following array. When they unfollow, pull it out. The atomic nature of these operators means two simultaneous follow/unfollow actions on the same document will be serialized correctly.

For large social graphs (millions of followers), this embedded array approach hits the 16 MB document limit. At scale, move relationships to a dedicated follows collection with followerId and followeeId fields.

// Follow a user
async function followUser(followerId, followeeId) {
  await db.collection('users').updateOne(
    { _id: new ObjectId(followerId) },
    { $addToSet: { following: new ObjectId(followeeId) } }
  );
  // Optionally: add to followee's followers array too
}

// Unfollow a user
async function unfollowUser(followerId, followeeId) {
  await db.collection('users').updateOne(
    { _id: new ObjectId(followerId) },
    { $pull: { following: new ObjectId(followeeId) } }
  );
}

The Positional $ Operator in Updates

When you need to update a specific element within an array that matched your query, use the positional $ operator. In the update document, $ refers to the first array element that matched the filter condition.

For example, to mark a specific order item as shipped, filter by { 'items.sku': 'A1' } and then update { : { 'items.$.shipped': true } }. The $ acts as a placeholder for the index of the matched array element. If multiple elements match, only the first is updated.

// Update a specific array element using positional operator
// Find order containing item sku 'A1', mark it shipped
db.orders.updateOne(
  { _id: orderId, 'items.sku': 'A1' },   // Filter: includes array condition
  { : { 'items.$.shipped': true } }  // $: matched array element index
);
// Before: items: [{sku:'A1',shipped:false},{sku:'B2',shipped:false}]
// After:  items: [{sku:'A1',shipped:true},{sku:'B2',shipped:false}]

Quick Check

Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.

Lesson Recap

In this lesson you learned: $push appends elements to an array and supports $each/$sort/$slice modifiers for maintaining capped sorted arrays like top-N lists, $pull removes all matching elements by value or condition—use $pullAll to remove multiple specific values at once, and $addToSet adds an element only if absent—ideal for maintaining unique sets like user roles, tags, and permissions without application-level duplicate checks. Next up we explore how to safely delete documents with deleteOne and deleteMany.

常见问题解答

「数组更新运算符:$push、$pull、$addToSet」课时是免费的吗?

是的 — 「数组更新运算符:$push、$pull、$addToSet」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 MongoDB Academy 课程的其余内容,请升级到 CoddyKit PRO。 MongoDB Academy 课程共包含 4 节课。

「数组更新运算符:$push、$pull、$addToSet」这节课中我会学到什么?

您将使用针对数组的更新运算符,向文档内的数组添加或移除项目。 你通过在浏览器中直接运行的动手代码来练习 MongoDB Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 MongoDB Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 MongoDB Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「数组更新运算符:$push、$pull、$addToSet」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 MongoDB Academy 课中编写并运行代码吗?

能。每节 MongoDB Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 $set 和 $unset 的 updateOne 与 updateMany
  2. 递增、乘法与最小值/最大值运算符
  3. 数组更新运算符:$push、$pull、$addToSet
  4. 使用 deleteOne 和 deleteMany 安全删除文档
← 返回 MongoDB Academy