Trên thực tế, điều này liên quan đến vấn đề tồn tại lâu dài tại http://jira.mongodb.org/browse/SERVER-1243 trong đó trên thực tế có một số thách thức đối với một cú pháp rõ ràng hỗ trợ "tất cả các trường hợp" trong đó các trận đấu mảng hai tìm. Trên thực tế, đã có những phương pháp "trợ giúp" cho các giải pháp cho vấn đề này, chẳng hạn như Hoạt động hàng loạt đã được triển khai sau bài đăng gốc này.
Vẫn không thể cập nhật nhiều hơn một phần tử mảng phù hợp trong một câu lệnh cập nhật duy nhất, vì vậy ngay cả với bản cập nhật "đa", tất cả những gì bạn có thể cập nhật chỉ là một phần tử được tính toán trong mảng cho mỗi tài liệu trong một tài liệu đó tuyên bố.
Giải pháp tốt nhất có thể hiện tại là tìm và lặp tất cả các tài liệu phù hợp và xử lý các cập nhật hàng loạt ít nhất sẽ cho phép nhiều hoạt động được gửi trong một yêu cầu với một phản hồi số ít. Bạn có thể tùy ý sử dụng .aggregate()
để giảm nội dung mảng được trả về trong kết quả tìm kiếm xuống chỉ còn những nội dung phù hợp với điều kiện lựa chọn cập nhật:
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();
Các .aggregate()
phần sẽ làm việc khi có một định danh "độc đáo" cho mảng hoặc toàn bộ nội dung cho mỗi phần tử tạo thành một "độc đáo" yếu tố riêng của mình. Điều này là do toán tử "set" $setDifference
được sử dụng để lọc bất kỳ false
giá trị nào được trả về từ $map
thao tác được sử dụng để xử lý mảng cho khớp.
Nếu nội dung mảng của bạn không có các yếu tố duy nhất, bạn có thể thử một cách tiếp cận thay thế với $redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
Trường hợp hạn chế là nếu "xử lý" trên thực tế là một trường có nghĩa là có mặt ở các cấp độ tài liệu khác thì bạn có thể sẽ nhận được kết quả không được phát hiện, nhưng sẽ ổn khi trường đó chỉ xuất hiện ở một vị trí tài liệu và là một kết quả khớp.
Các bản phát hành trong tương lai (bài 3.1 MongoDB) khi viết sẽ có một $filter
thao tác đơn giản hơn:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
Và tất cả các bản phát hành hỗ trợ .aggregate()
có thể sử dụng cách tiếp cận sau $unwind
, nhưng việc sử dụng toán tử đó làm cho nó trở thành cách tiếp cận kém hiệu quả nhất do mở rộng mảng trong đường ống:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
Trong tất cả các trường hợp phiên bản MongoDB hỗ trợ "con trỏ" từ đầu ra tổng hợp, thì đây chỉ là vấn đề chọn cách tiếp cận và lặp lại kết quả với cùng một khối mã được hiển thị để xử lý các câu lệnh cập nhật hàng loạt. Hoạt động hàng loạt và "con trỏ" từ đầu ra tổng hợp được giới thiệu trong cùng một phiên bản (MongoDB 2.6) và do đó thường làm việc cùng nhau để xử lý.
Trong các phiên bản thậm chí trước đó, có lẽ tốt nhất là chỉ sử dụng .find()
để trả về con trỏ và lọc ra việc thực hiện các câu lệnh để chỉ số lần phần tử mảng được khớp cho các .update()
lần lặp:
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 }});
});
});
Nếu bạn hoàn toàn quyết tâm thực hiện cập nhật "đa" hoặc cho rằng cuối cùng sẽ hiệu quả hơn so với xử lý nhiều cập nhật cho mỗi tài liệu phù hợp, thì bạn luôn có thể xác định số lượng tối đa của các kết quả khớp có thể và chỉ thực hiện cập nhật "nhiều" lần, cho đến khi về cơ bản không có thêm tài liệu để cập nhật.
Một cách tiếp cận hợp lệ cho các phiên bản MongoDB 2.4 và 2.2 cũng có thể được sử dụng .aggregate()
để tìm giá trị này:
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 })
}
Dù thế nào đi nữa, có một số điều bạn không muốn làm trong bản cập nhật:
Không "cập nhật một lần" cập nhật mảng: Nếu bạn nghĩ rằng có thể hiệu quả hơn để cập nhật toàn bộ nội dung mảng trong mã và sau đó chỉ $set
toàn bộ mảng trong mỗi tài liệu. Điều này có vẻ nhanh hơn để xử lý, nhưng không có gì đảm bảo rằng nội dung mảng không thay đổi kể từ khi nó được đọc và cập nhật được thực hiện. Mặc dù $set
vẫn là một toán tử nguyên tử, nó sẽ chỉ cập nhật mảng với những gì nó "nghĩ" là dữ liệu chính xác và do đó có khả năng ghi đè lên bất kỳ thay đổi nào xảy ra giữa đọc và ghi.
Không tính toán các giá trị chỉ mục để cập nhật: Trường hợp tương tự như cách tiếp cận "một lần bắn", bạn chỉ cần tìm ra vị trí 0
và vị trí đó 2
(v.v.) là các yếu tố để cập nhật và mã hóa chúng theo và tuyên bố cuối cùng như:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Một lần nữa, vấn đề ở đây là "giả định" rằng các giá trị chỉ mục được tìm thấy khi tài liệu được đọc là cùng các giá trị chỉ mục trong mảng thứ tại thời điểm cập nhật. Nếu các mục mới được thêm vào mảng theo cách thay đổi thứ tự thì các vị trí đó không còn hiệu lực và thực tế các mục sai được cập nhật.
Vì vậy, cho đến khi có một cú pháp hợp lý được xác định để cho phép xử lý nhiều phần tử mảng phù hợp trong câu lệnh cập nhật duy nhất thì cách tiếp cận cơ bản là cập nhật từng phần tử mảng phù hợp trong một câu lệnh ngẫu nhiên (lý tưởng là Hàng loạt) hoặc về cơ bản xử lý các phần tử mảng tối đa để cập nhật hoặc tiếp tục cập nhật cho đến khi không có kết quả sửa đổi nào được trả về. Ở bất kỳ giá nào, bạn nên "luôn luôn" xử lý các$
cập nhật vị trí trên phần tử mảng phù hợp, ngay cả khi đó chỉ cập nhật một phần tử cho mỗi câu lệnh.
Các hoạt động hàng loạt trên thực tế là giải pháp "tổng quát" để xử lý bất kỳ hoạt động nào hoạt động thành "nhiều hoạt động" và vì có nhiều ứng dụng cho việc này hơn là chỉ cập nhật các phần tử mảng đa biến có cùng giá trị, nên dĩ nhiên nó đã được thực hiện và đây là cách tiếp cận tốt nhất để giải quyết vấn đề này.