$addtoset仅对bson全等值去重,基础类型正常,但嵌套文档需字段顺序、类型、值完全一致才视为重复;按业务字段(如uid)去重须改用应用层控制或$pull+$push组合。

$push 无条件追加,$addToSet 只在值不存在时插入 —— 但“存在”的判定规则很关键,容易误判。
$push 就是往数组末尾硬塞一个新元素
不管字段是否存在、数组是否为空、值是否已存在,$push 都直接追加。它不查重,也不关心语义。
- 如果目标字段不存在,会自动创建一个数组并插入值
- 如果字段已存在且是数组,就在末尾添加新元素(哪怕和已有元素完全一样)
- 如果字段存在但不是数组,操作会失败,报错
Cannot apply $push to a non-array value - 对嵌套文档使用
$push时,整个文档必须完全相等才算重复 —— 顺序、键名、缺失字段都影响判断
$addToSet 看似去重,但只对“全等值”生效
$addToSet 不是按业务逻辑去重,而是按 BSON 值全等(strict equality)判断是否已存在。只要值完全一致,就跳过插入。
- 基本类型(字符串、数字、布尔)没问题:
"tag"已在数组里,再$addToSet同值不会重复 - 数组类型也支持:但要求元素个数、顺序、每个元素值都一致才视为重复
- **嵌套文档最易踩坑**:
{uid: 10001, addtime: 1532676854771}和{uid: 10001, addtime: 1532676854770}被认为是两个不同文档,即使 uid 相同也会被同时插入 - 字段顺序不同也算不同文档:
{a: 1, b: 2}≠{b: 2, a: 1}
想真正按某个字段去重?别依赖 $addToSet
当你要基于 uid 去重好友、基于 email 去重订阅者,$addToSet 无法满足 —— 它没法只比对子字段。
- 常见错误写法:
$addToSet: { friends: { uid: 10001, addtime: ... } }→ 多次调用会累积多个相同uid的记录 - 正确思路:先
$pull掉旧的,再$push新的;或用$set+ 数组索引更新(需配合$操作符) - 聚合阶段可用
$reduce或$group实现逻辑去重,但不能用于 update - 应用层控制更稳妥:读出当前数组 → 过滤掉已有
uid→ 构造新数组 →$set整体替换
批量添加时 $each 的行为差异
两者都支持 $each,但结果逻辑不同:
-
$push: { tags: { $each: ["a", "b", "c"] } }→ 总是追加全部三个值,不管原来有没有 -
$addToSet: { tags: { $each: ["a", "b", "c"] } }→ 对每个值单独做存在性检查,只插入那些不在数组里的 - 注意:
$addToSet+$each不会把整个输入数组当一个元素插进去;而漏掉$each直接写$addToSet: { tags: ["a","b"] }会把["a","b"]当成一个嵌套数组插进去
真正麻烦的从来不是语法,而是你心里以为的“重复”和 MongoDB 认为的“重复”根本不是一回事。尤其处理带时间戳或动态字段的嵌套文档时,$addToSet 几乎总是不如手动控制可靠。











