直接用$push会撑爆数组,因其无自动截断能力,必须与$each和$slice嵌套使用才能原子保留最新N条,如{$push:{arr:{$each:[x],$slice:-3}}}。

为什么直接用 $push 会撑爆数组
当你反复用 $push 往一个数组字段追加元素,它只会不断增长,不自动清理旧数据。比如日志、操作历史这类场景,很快就会让单文档超过 16MB 限制,或拖慢查询性能。
关键点在于:$push 本身不带截断能力,必须配合 $slice 才能实现“只留最新 N 条”——但二者必须在同一更新操作中嵌套使用,不能分两步。
-
$push必须带上$each和$slice,三者是原子绑定关系 -
$slice的值为负数(如-5)表示保留末尾 N 个元素,即最新插入的 N 条 - 不能对已有数组单独执行
$slice;它只是$push的修饰符,不是独立更新器
$push + $slice 的正确写法长什么样
假设集合 users 中每个文档有个 recent_actions 数组,你想每次插入新动作并只保留最近 3 条:
db.users.updateOne(
{ _id: ObjectId("...") },
{
$push: {
recent_actions: {
$each: [{ action: "login", at: new Date() }],
$slice: -3
}
}
}
)
注意几个细节:
-
$each是必需的,哪怕只推一个元素;不写$each就无法启用$slice -
$slice: -3表示“保留数组末尾 3 个”,等价于“丢弃前面所有”,这才是“最新 N 条”的语义 - 如果想保留最多 N 条(不足 N 也不补),就用负数;正数
$slice: 3是保留前 3 条(最老的),通常不是你想要的
常见翻车现场:$slice 不生效的几种情况
很多人写了 $slice 却发现数组还在疯长,大概率踩了下面这些坑:
- 漏写
$each:写成{ recent_actions: { $push: { $slice: -5 } } }—— 这语法错误,MongoDB 直接报"$slice is not allowed to be used with $push without $each" - 把
$slice放错层级:写在$push外面(如和$push并列),MongoDB 会忽略它 - 对空数组首次插入时误用正数
$slice:比如$slice: 5,结果只存了 1 条还卡着不动,因为正数是“从头取”,不是“最多存” - 在事务或多阶段 pipeline update 中混用
$set和$push,但没把$slice绑定到对应$push字段上
性能与兼容性要注意什么
这个组合在大多数现代 MongoDB 版本(4.2+)里表现稳定,但仍有边界需留意:
- 数组长度剧烈波动时(比如某次推 100 个元素,
$slice: -5),$push+$slice会先扩展再裁剪,内存临时占用略高,不过不影响原子性 - MongoDB 4.0 及更早版本不支持在同一个
$push中同时用$each和$slice;必须升到 4.2+ - 如果字段可能不存在,
$push会自动创建数组,无需预设字段;但若该字段已存在且不是数组类型,操作会失败
真正容易被忽略的是:这个机制只管“数量”,不管“时间”。如果你需要按时间戳自动淘汰过期条目(比如 1 小时内的记录),就得换用 TTL 索引或应用层控制——$slice 不看内容,只认位置。

















