$pull 是删除数组中匹配元素的首选方法,语义清晰、性能好,支持等值匹配和简单表达式,但不支持复杂逻辑和嵌套数组;需用 $filter + $set 或 arrayFilters 处理更复杂场景。

使用 $pull 删除数组中匹配的元素最直接
如果目标只是删掉数组里符合某个条件的项,$pull 是首选,语义清晰、写法简单、性能也好。它原地过滤并移除匹配项,不涉及重构造整个数组。
常见错误是误用 $set 配合 $filter 去“模拟删除”,结果多写几行还容易出错。比如想删掉 tags 数组中值为 "deprecated" 的元素:
db.collection.updateOne(
{ _id: ObjectId("...") },
{ $pull: { tags: "deprecated" } }
)
-
$pull支持等值匹配、文档字段匹配(如{ status: "inactive" })、甚至简单表达式({ score: { $lt: 50 } }) - 不支持复杂逻辑(比如“取数组倒数第二个元素且满足某条件”),这时才需转向
$filter - 对嵌套数组(如
comments.replies)无效,$pull只作用于顶层字段或点号路径直达的数组字段
用 $filter + $set 重构数组时必须显式指定输入源
$filter 不是独立操作符,必须嵌在 $set 或聚合管道更新中,且必须通过 input 明确告诉它处理哪个数组——不能默认作用于字段名本身。
典型翻车现场:写成 { $filter: { cond: { $ne: ["$$this", "temp"] } } },漏了 input,MongoDB 直接报错 "input" must be a valid expression。
db.collection.updateOne(
{ _id: ObjectId("...") },
[
{
$set: {
tags: {
$filter: {
input: "$tags",
cond: { $ne: ["$$this", "temp"] }
}
}
}
}
]
)
- 必须用
"$tags"这样的字段路径作为input,不能写tags或"tags" -
cond中的$$this指当前遍历元素,别名不可改;想引用外层字段得用$$ROOT.field或提前用$let - 聚合管道更新(方括号语法)从 MongoDB 4.2+ 才支持,4.0 及以下只能用旧版
$set+$map/$reduce组合,更绕
嵌套数组删除必须用 arrayFilters 或多级 $filter
当要删的是子文档数组里的元素(例如每个 items 下的 logs),$pull 无法跨两级定位。这时要么用 arrayFilters,要么用两层 $filter。
比如删掉所有 items 中 logs 里 level: "debug" 的日志:
db.collection.updateMany(
{},
{
$set: {
"items.$[item].logs": {
$filter: {
input: "$$item.logs",
cond: { $ne: ["$$this.level", "debug"] }
}
}
}
},
{ arrayFilters: [{ "item.logs.level": "debug" }] }
)
-
arrayFilters是声明式限定,只影响匹配到的items元素;没加它,$[item]会报错 - 若不用
arrayFilters,就得在外层再套一层$map+$filter,代码膨胀且难读 -
$$item是arrayFilters定义的变量名,和$[item]里的名字必须一致
性能与兼容性:$pull 比 $filter 更轻量,但表达能力有限
$pull 在存储引擎层优化充分,尤其对小数组或高频等值删除场景,延迟低、锁粒度小。$filter 走聚合执行器,会把整个数组加载进内存做遍历,大数据量时可能触发 Expression exceeds maximum size 错误。
- 数组长度超 10MB 或含大量嵌套文档时,优先拆分逻辑或预过滤
- MongoDB 5.0+ 对
$filter做了部分下推优化,但不如$pull稳定 - 驱动兼容性没问题,但旧版本 Shell(如 4.0.x)不支持聚合管道更新语法,得降级用
find + bulkWrite替代
真正需要 $filter 的时刻,往往不是“删元素”,而是“提取符合条件的子集后做后续计算”——这时候它才不可替代。单纯删,先试试 $pull。











