$push + $each 报错“cannot apply $each modifier to non-array”是因为目标字段必须是数组类型,若字段不存在、为null、string或number等非数组值,MongoDB会拒绝操作;安全做法是预先用$set初始化为空数组或用$setOnInsert确保字段存在。
直接对一个不存在或值为 null、string、number 的字段执行 $push 配合 $each,MongoDB 会拒绝操作并抛出该错误。它要求目标字段必须是数组类型,且不能是 undefined 或其他非数组值。
常见触发场景:文档首次插入时没初始化数组字段;或者之前用 $set 覆盖成了非数组值。
$setOnInsert 或先执行一次 $set: { tags: [] }
$ifNull 在聚合管道式更新(4.2+)中做类型兜底,但普通 updateOne 不支持find 查看实际值,或用 $addToSet 替代(但不保证顺序、去重)$each 是 $push 的子修饰符,用于指定要追加的元素列表,MongoDB 会按数组顺序逐个追加到目标数组末尾,顺序严格保留。
示例:给用户文档的 history 数组一次性添加三条操作记录:
db.users.updateOne( { _id: ObjectId("...") }, { $push: { history: { $each: [ { action: "login", at: ISODate("2024-05-01T08:00:00Z") }, { action: "view", at: ISODate("2024-05-01T08:05:00Z") }, { action: "search", at: ISODate("2024-05-01T08:10:00Z") } ] } } })
$each 必须嵌套在 $push 对象内,不能平级写成 { $push: ..., $each: [...] }
$each 配合 $position: 0
$position 指定插入位置索引(从 0 开始),$slice 控制最终数组长度,二者可同时与 $each 共存,实现“头部插入 + 保底截断”等策略。
例如:只保留最近 5 条日志,新日志插到开头:
db.logs.updateOne( { _id: 123 }, { $push: { entries: { $each: [ { msg: "new error" } ], $position: 0, $slice: -5 } } })
$position: 0 表示插入到索引 0 位置(即最前面);负数索引不支持$slice: -5 表示保留最后 5 项(即从尾部截取);$slice: 5 是保留前 5 项$position 和 $slice 必须和 $each 在同一层级,都属于 $push 的子对象$slice 而不用 $each,则仅做截断,不新增元素Node.js MongoDB 驱动(v4.0+)本身不修改你传入的 JS 数组,但容易因引用或变量复用导致意外行为。
$push,比如 const items = [...]; update(..., { $each: items }) → 后续修改 items 会影响下一次更新内容Date 或 ObjectId 实例,而非字符串;驱动不会自动转换updateMany 批量更新时,$each 内容对每个匹配文档都相同;如需差异化数据,必须用 bulkWrite 分条构造真正麻烦的不是语法,而是数组字段的初始态和并发写入时的竞态——比如两个请求同时发现字段为空,各自初始化再 push,结果只剩一个生效。这种场景得靠应用层加锁或改用 findAndModify 做原子判断+更新。