数组字段怎么更新?$push、$pull、$addToSet 各自做什么?$ 和 $[] 又怎么选?这篇逐个验证,并用 8 个线程对比原子 $push 和读-改-写。
test> $push: {v: 2}
[ 3, 1, 2 ]
test> $push: {v: {$each: [9, 8], $position: 0}}
[ 9, 8, 3, 1, 2 ]
test> $push: {v: {$each: [7, 6], $sort: -1, $slice: 4}} // 追加后降序排序,只保留前 4 个
[ 9, 8, 7, 6 ]
test> $push: {v: {$each: [], $slice: -2}} // 只保留最后 2 个
[ 7, 6 ]$each 一次加多个,$position 指定插入位置,$sort 与 $slice 在追加后排序并截断,常用来保留最近 N 条。
test> $addToSet: {s: "x"} // 已存在
0 / ["x","y"]
test> $addToSet: {s: {$each: ["y","z","w"]}}
[ 'x', 'y', 'z', 'w' ]
test> $pull: {n: {$gt: 7}}
[ 1, 5, 3 ]
test> $pullAll: {n: [1, 3]}
[ 5 ]
test> $pop: {n: 1} 然后 $pop: {n: -1} // 先删最后一个,再删第一个
[ 2, 3 ]$addToSet 有集合语义,$pull 按条件删,$pullAll 按值删,$pop 删头或尾。
test> {"g.k": "a"} + $set: {"g.$.v": 99} // 只改第一个 k=a
[ 'a:99', 'b:1', 'a:1' ]
test> $inc: {"g.$[].v": 10} // 所有元素
[ 'a:109', 'b:11', 'a:11' ]
test> $set: {"g.$[e].v": 0}, arrayFilters: [{"e.k": "a"}] // 所有 k=a 的元素
[ 'a:0', 'b:11', 'a:0' ]
test> $ 但查询条件里没有数组字段
Plan executor error during update :: caused by :: The positional operator did not find the match needed from the query.$ 只改查询匹配到的第一个元素(查询里必须有数组条件);$[] 改全部;$[e] 加 arrayFilters 改满足条件的所有元素。
test> $push 到字符串字段
Plan executor error during update :: caused by :: The field 's' must be an array but is of type string in document {_id: 1}
test> $push 到缺失的字段
[ 1 ]
test> $pull 不存在的值
0对非数组字段 $push 报错;字段缺失时 $push 会创建数组;$pull 不存在的值不报错。
test> $addToSet: {s: {b: 2, a: 1}} // 内容相同,字段顺序不同
2 个元素
test> $addToSet: {s: {a: 1, b: 2}} // 顺序相同
2 个元素$addToSet 比较子文档要求字段顺序一致,内容相同但顺序不同的子文档会被当成新元素加进去。
原子的 $push:数组长度 800(应为 800) 读-改-写:数组长度 153(应为 800,丢掉的是并发时被覆盖的)
单个文档内的 $push 是原子的,8 线程 800 次追加一个不丢;读出来再写回去的做法在并发下大量丢失。