$push 用于向数组追加元素,字段不存在时自动创建数组,非数组字段则报错;支持 $each、$slice、$sort、$position 等修饰符实现批量添加、长度限制、排序和指定位置插入。

直接用 $push 就能追加,但得注意字段是否存在
如果目标字段不存在,$push 会自动创建它并设为数组;如果已存在但不是数组,操作会报错 Path xxx must be an array。所以别假设字段一定就绪,尤其在混合 schema 场景下。
最稳妥的写法是配合 $setOnInsert 或先用 $exists 检查,但日常更新中更常用的是直接 $push 并接受“自动建数组”的行为:
db.users.updateOne(
{ _id: 123 },
{ $push: { tags: "admin" } }
)
这行代码会让 tags 变成 ["admin"](若原来不存在),或在末尾追加 "admin"(若已是数组)。
$push 的常见变体:去重、限制长度、插入到开头
单纯追加太基础,实际常要控制行为。MongoDB 提供了几个修饰符,必须和 $push 一起用,不能单独出现:
-
$each:一次推多个元素,必须配合使用,不能直接$push: [1,2,3] -
$slice:限制数组最终长度,负数表示保留末尾若干项(常用于日志截断) -
$sort:配合$slice使用,对数组排序后再截取 -
$position:指定插入位置,$position: 0表示插到开头
例如,只保留最近 5 条操作记录,并按时间倒序排列:
db.logs.updateOne(
{ userId: 456 },
{
$push: {
history: {
$each: [{ time: new Date(), action: "login" }],
$slice: -5,
$sort: { time: -1 }
}
}
}
)
为什么 $push 有时不生效?检查这三点
不是语法错,而是逻辑或权限问题导致静默失败:
- 匹配文档不存在:update 默认不创建文档,除非加
{ upsert: true }。没匹配上,$push就什么都不做 - 字段被定义为不可变(如某些 ODM 框架加了 schema 约束),或集合启用了严格模式(如 MongoDB Atlas 的 Schema Validation)
- 用户权限不足——比如只有
find权限,没有update权限,命令返回成功但实际无变更
调试时先用 find 确认文档存在,再用 updateOne 的 result.matchedCount 和 modifiedCount 判断是否真改了。
数组太大时 $push 的性能影响
每次 $push 都会重写整个文档(即使只改一个数组元素),所以数组越大,写放大越明显。当单个文档接近 16MB 上限,或数组频繁增删时,就得考虑拆分:
- 把数组内容移到独立集合,用引用代替内嵌(例如
user_id字段关联user_tags集合) - 用
$addToSet替代$push避免重复,但注意它不保证顺序 - 批量写入时,合并多次
$push为一次$each,减少网络往返和锁竞争
内嵌数组适合小而稳定的列表(如用户角色、标签),不适合动态增长的事件流或消息记录。











