这实际上与http://jira.mongodb.org/browse/SERVER-1243的长期存在的问题有关,事实上,在支持多个数组匹配的“所有情况”的清晰语法方面存在许多挑战成立。事实上,已经有一些方法可以“帮助”解决这个问题,例如在这篇原始帖子之后实施的批量操作。
仍然不可能在单个更新语句中更新多个匹配的数组元素,因此即使使用“多”更新,您将能够更新的只是该单个文档中每个文档的数组中的一个数学元素陈述。
目前最好的解决方案是查找并循环所有匹配的文档并处理批量更新,这将至少允许在单个请求中发送许多操作和单个响应。您可以选择使用.aggregate()
将搜索结果中返回的数组内容减少为仅与更新选择条件匹配的内容:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$setDifference": [
{ "$map": {
"input": "$events",
"as": "event",
"in": {
"$cond": [
{ "$eq": [ "$$event.handled", 1 ] },
"$$el",
false
]
}
}},
[false]
]
}
}}
]).forEach(function(doc) {
doc.events.forEach(function(event) {
bulk.find({ "_id": doc._id, "events.handled": 1 }).updateOne({
"$set": { "events.$.handled": 0 }
});
count++;
if ( count % 1000 == 0 ) {
bulk.execute();
bulk = db.collection.initializeOrderedBulkOp();
}
});
});
if ( count % 1000 != 0 )
bulk.execute();
.aggregate()
当数组有一个“唯一”标识符或每个元素的所有内容形成一个“唯一”元素本身时,该部分将起作用。这是由于“set”运算符$setDifference
用于过滤从用于处理数组以进行匹配false
的操作返回的任何值。$map
如果您的数组内容没有唯一元素,您可以尝试另一种方法$redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
它的局限性在于,如果“已处理”实际上是一个本应出现在其他文档级别的字段,那么您可能会得到意想不到的结果,但如果该字段仅出现在一个文档位置并且是相等匹配,则很好。
在撰写本文时,未来的版本(3.1 MongoDB 后)将有一个$filter
更简单的操作:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
并且所有支持的版本都.aggregate()
可以使用以下方法 with $unwind
,但是由于管道中的数组扩展,该运算符的使用使其成为效率最低的方法:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
在 MongoDB 版本支持来自聚合输出的“光标”的所有情况下,这只是选择一种方法并使用显示的用于处理批量更新语句的相同代码块迭代结果的问题。来自聚合输出的批量操作和“游标”在同一版本 (MongoDB 2.6) 中引入,因此通常携手处理。
在更早的版本中,最好只使用.find()
返回游标,并将语句的执行过滤到数组元素与.update()
迭代匹配的次数:
db.collection.find({ "events.handled": 1 }).forEach(function(doc){
doc.events.filter(function(event){ return event.handled == 1 }).forEach(function(event){
db.collection.update({ "_id": doc._id },{ "$set": { "events.$.handled": 0 }});
});
});
如果您绝对确定要进行“多次”更新或认为最终比为每个匹配的文档处理多次更新更有效,那么您始终可以确定可能的数组匹配的最大数量并执行“多次”更新次,直到基本上没有更多文件要更新。
MongoDB 2.4 和 2.2 版本的有效方法也可以.aggregate()
用于查找此值:
var result = db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": null,
"count": { "$max": "$count" }
}}
]);
var max = result.result[0].count;
while ( max-- ) {
db.collection.update({ "events.handled": 1},{ "$set": { "events.$.handled": 0 }},{ "multi": true })
}
无论如何,您不想在更新中执行某些操作:
不要“一次性”更新数组:如果您认为更新代码中的整个数组内容然后只更新$set
每个文档中的整个数组可能更有效。这似乎处理起来更快,但不能保证数组内容自读取并执行更新以来没有更改。虽然$set
它仍然是一个原子操作符,但它只会使用它“认为”正确的数据来更新数组,因此很可能会覆盖读取和写入之间发生的任何更改。
不要计算要更新的索引值:与“一次性”方法类似,您只需计算出位置0
和位置2
(等等)是要更新和编码的元素,最终语句如下:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
这里的问题再次是“假设”,即在读取文档时找到的那些索引值与更新时数组中的索引值相同。如果以更改顺序的方式将新项目添加到数组中,则这些位置不再有效,并且实际上更新了错误的项目。
因此,在确定允许在单个更新语句中处理多个匹配的数组元素的合理语法之前,基本方法是在单个语句中更新每个匹配的数组元素(理想情况下在 Bulk 中)或基本上计算出最大数组元素更新或继续更新,直到不再返回修改后的结果。无论如何,您应该“始终”处理匹配数组元素的位置$
更新,即使每个语句只更新一个元素。
批量操作实际上是处理任何“多个操作”的操作的“通用”解决方案,并且由于对此有更多的应用程序,而不仅仅是更新具有相同值的多个数组元素,那么它当然已经实现已经,而且它是目前解决这个问题的最佳方法。