这实际上与http://jira.mongodb.org/browse/SERVER-1243上长期存在的问题有关,在http://jira.mongodb.org/browse/SERVER-1243上,对于支持找到多个数组匹配的“所有情况”的清晰语法,实际上存在许多挑战。事实上,已经有一些“帮助”解决这个问题的方法,比如在这篇原始文章之后实施的批量操作。
在一条更新语句中仍然不可能更新多个匹配的数组元素,因此即使使用“multi”更新,您也只能在该语句中为每个文档更新数组中的一个数学元素。
目前可能的最佳解决方案是查找并循环所有匹配的文档,并处理Bulk更新,这至少将允许在单个请求中以单个响应发送多个操作。您可以选择使用.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()部分将工作。这是由于$setDifference中的“set”操作符用于过滤从用于处理匹配数组的$map操作返回的任何错误值。
如果你的数组内容没有唯一的元素,你可以用$ react尝试另一种方法:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
它的局限性在于,如果“handled”实际上是一个应该出现在其他文档级别的字段,那么您可能会得到意想不到的结果,但如果该字段只出现在一个文档位置,并且是相等匹配的,则没问题。
未来的版本(3.1之后的MongoDB)在编写时将有一个更简单的$filter操作:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
所有支持.aggregate()的版本都可以使用以下方法来处理$unwind,但由于管道中的数组扩展,该操作符的使用使其成为效率最低的方法:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
在MongoDB版本支持来自汇总输出的“游标”的所有情况下,这只是选择一种方法,并使用显示用于处理Bulk更新语句的相同代码块迭代结果的问题。在同一个版本(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 }});
});
});
如果您决心要进行“multi”更新,或者认为这最终比为每个匹配的文档处理多个更新更有效,那么您总是可以确定可能匹配的数组的最大数量,并执行多次“multi”更新,直到基本上没有更多的文档需要更新为止。
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 })
}
无论哪种情况,在更新中有一些事情是你不想做的:
Do not "one shot" update the array: Where if you think it might be more efficient to update the whole array content in code and then just $set the whole array in each document. This might seem faster to process, but there is no guarantee that the array content has not changed since it was read and the update is performed. Though $set is still an atomic operator, it will only update the array with what it "thinks" is the correct data, and thus is likely to overwrite any changes occurring between read and write.
Do not calculate index values to update: Where similar to the "one shot" approach you just work out that position 0 and position 2 ( and so on ) are the elements to update and code these in with and eventual statement like:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Again the problem here is the "presumption" that those index values found when the document was read are the same index values in th array at the time of update. If new items are added to the array in a way that changes the order then those positions are not longer valid and the wrong items are in fact updated.
So until there is a reasonable syntax determined for allowing multiple matched array elements to be processed in single update statement then the basic approach is to either update each matched array element in an indvidual statement ( ideally in Bulk ) or essentially work out the maximum array elements to update or keep updating until no more modified results are returned. At any rate, you should "always" be processing positional $ updates on the matched array element, even if that is only updating one element per statement.
批量操作实际上是处理“多个操作”的任何操作的“通用”解决方案,由于有更多的应用程序用于此,而不仅仅是更新具有相同值的多个数组元素,因此它当然已经实现了,并且它是目前解决这个问题的最佳方法。