W rzeczywistości odnosi się to do długotrwałego problemu na stronie http://jira.mongodb.org/browse/SERVER-1243, gdzie w rzeczywistości istnieje wiele wyzwań dla jasnej składni, która obsługuje „wszystkie przypadki”, w których możliwe jest dopasowanie wielu tablic znaleziony. W rzeczywistości istnieją już metody „pomocy” w rozwiązaniu tego problemu, takie jak operacje masowe, które zostały wdrożone po tym oryginalnym poście.
Nadal nie jest możliwe zaktualizowanie więcej niż jednego dopasowanego elementu tablicy w pojedynczej instrukcji aktualizacji, więc nawet przy aktualizacji „wielu” wszystko, co kiedykolwiek będziesz w stanie zaktualizować, to tylko jeden dopasowany element w tablicy dla każdego dokumentu w tym pojedynczym komunikat.
Obecnie najlepszym możliwym rozwiązaniem jest znalezienie i zapętlenie wszystkich dopasowanych dokumentów oraz przetworzenie aktualizacji zbiorczych, co pozwoli przynajmniej na wysłanie wielu operacji w jednym żądaniu z pojedynczą odpowiedzią. Opcjonalnie możesz użyć, .aggregate()
aby zmniejszyć zawartość tablicy zwróconej w wyniku wyszukiwania do tych, które pasują do warunków wyboru aktualizacji:
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()
Część nie będzie działać, gdy jest „Unikalny” identyfikator tablicy lub całej zawartości każdego elementu tworzy „niepowtarzalną” samego elementu. Wynika to z operatora „set” $setDifference
używanego do filtrowania wszelkich false
wartości zwracanych z $map
operacji użytej do przetworzenia tablicy pod kątem dopasowań.
Jeśli zawartość tablicy nie zawiera unikalnych elementów, możesz spróbować zastosować alternatywne podejście z $redact
:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$redact": {
"$cond": {
"if": {
"$eq": [ { "$ifNull": [ "$handled", 1 ] }, 1 ]
},
"then": "$$DESCEND",
"else": "$$PRUNE"
}
}}
])
Ograniczeniem jest to, że jeśli „obsłużone” było w rzeczywistości polem przeznaczonym do obecności na innych poziomach dokumentu, prawdopodobnie uzyskasz nieoczekiwane wyniki, ale jest dobrze, gdy to pole pojawia się tylko w jednej pozycji dokumentu i jest równe.
Przyszłe wersje (po 3.1 MongoDB) w momencie pisania będą miały $filter
prostszą operację:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$project": {
"events": {
"$filter": {
"input": "$events",
"as": "event",
"cond": { "$eq": [ "$$event.handled", 1 ] }
}
}
}}
])
We wszystkich wersjach, które obsługują, .aggregate()
można stosować następujące podejście $unwind
, ale użycie tego operatora sprawia, że jest to najmniej wydajne podejście ze względu na rozszerzenie tablicy w potoku:
db.collection.aggregate([
{ "$match": { "events.handled": 1 } },
{ "$unwind": "$events" },
{ "$match": { "events.handled": 1 } },
{ "$group": {
"_id": "$_id",
"events": { "$push": "$events" }
}}
])
We wszystkich przypadkach, w których wersja MongoDB obsługuje „kursor” ze zbiorczych danych wyjściowych, jest to tylko kwestia wyboru podejścia i iteracji wyników z tym samym blokiem kodu pokazanym w celu przetworzenia instrukcji aktualizacji zbiorczej. Operacje zbiorcze i „kursory” z agregowanych danych wyjściowych są wprowadzane w tej samej wersji (MongoDB 2.6), a zatem zwykle działają równolegle do przetwarzania.
W nawet wcześniejszych wersjach prawdopodobnie najlepiej użyć po prostu, .find()
aby zwrócić kursor i odfiltrować wykonanie instrukcji tylko tyle razy, ile element tablicy jest dopasowany do .update()
iteracji:
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 }});
});
});
Jeśli jesteś bezwzględnie zdeterminowany do wykonywania „wielu” aktualizacji lub uważasz, że jest to ostatecznie bardziej wydajne niż przetwarzanie wielu aktualizacji dla każdego dopasowanego dokumentu, zawsze możesz określić maksymalną liczbę możliwych dopasowań tablic i po prostu wykonać aktualizację „wielu” razy, aż w zasadzie nie ma już żadnych dokumentów do zaktualizowania.
Prawidłowe podejście do wersji MongoDB 2.4 i 2.2 może również posłużyć .aggregate()
do znalezienia tej wartości:
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 })
}
Niezależnie od przypadku, istnieją pewne rzeczy, których nie chcesz robić w ramach aktualizacji:
Nie „jednym strzałem” aktualizuj tablicę: jeśli uważasz, że bardziej wydajna może być aktualizacja całej zawartości tablicy w kodzie, a następnie tylko $set
całej tablicy w każdym dokumencie. Może się to wydawać szybsze w przetwarzaniu, ale nie ma gwarancji, że zawartość tablicy nie zmieniła się od czasu jej odczytania i przeprowadzenia aktualizacji. Chociaż $set
nadal jest operatorem atomowym, zaktualizuje tablicę tylko o to, co „uważa” za poprawne dane, a zatem prawdopodobnie zastąpi wszelkie zmiany zachodzące między odczytem a zapisem.
Nie obliczaj wartości indeksu do aktualizacji: tam, gdzie jest to podobne do podejścia „jednego strzału”, po prostu ustal, że pozycja 0
i pozycja 2
(i tak dalej) są elementami do aktualizacji i zakodowania ich za pomocą i ewentualne stwierdzenie takie jak:
{ "$set": {
"events.0.handled": 0,
"events.2.handled": 0
}}
Ponownie problemem jest tutaj „domniemanie”, że wartości indeksu znalezione podczas odczytu dokumentu są tymi samymi wartościami indeksu w tablicy w momencie aktualizacji. Jeśli nowe elementy zostaną dodane do tablicy w sposób zmieniający kolejność, pozycje te nie będą już ważne, a niewłaściwe elementy zostaną w rzeczywistości zaktualizowane.
Tak więc, dopóki nie zostanie ustalona rozsądna składnia umożliwiająca przetwarzanie wielu dopasowanych elementów tablicy w pojedynczej instrukcji aktualizacji, wówczas podstawowym podejściem jest albo aktualizacja każdego dopasowanego elementu tablicy w pojedynczej instrukcji (najlepiej luzem), albo zasadniczo wypracowanie maksymalnych elementów tablicy aktualizować lub aktualizować, dopóki nie zostaną zwrócone żadne zmodyfikowane wyniki. W każdym razie powinieneś „zawsze” przetwarzać aktualizacje pozycyjne$
na dopasowanym elemencie tablicy, nawet jeśli aktualizuje to tylko jeden element na instrukcję.
Operacje zbiorcze są w rzeczywistości „uogólnionym” rozwiązaniem przetwarzania wszelkich operacji, które okazują się być „wieloma operacjami”, a ponieważ jest do tego więcej aplikacji niż tylko aktualizowanie elementów tablicy o tej samej wartości, to oczywiście zostało ono zaimplementowane już teraz i jest to obecnie najlepsze podejście do rozwiązania tego problemu.