Вычислить максимальное значение в атомарной операции FindAndModify - PullRequest
2 голосов
/ 14 декабря 2011

Я пытаюсь выяснить, возможно ли в MongoDB выдвинуть элемент и одновременно (атомарная FindAndModify операция) обновить максимальное значение элементов в массиве.

Пример:

{
  "_id": "...",
  "max_value": 10,
  "values": [2, 10, 6]
}

И после того, как я вставлю 20, результат будет:

{
  "_id": "...",
  "max_value": 20,
  "values": [2, 10, 6, 20]
}

Значение 20 помещено в массив значений, и поле max_value пересчитано (будет 20)та же атомарная операция.

Возможно ли это?

1 Ответ

4 голосов
/ 14 декабря 2011

РЕДАКТИРОВАТЬ: После дальнейших размышлений мой первоначальный ответ был правильным, но расточительным. В частности, первый шаг не является необходимым, поэтому вот пересмотренная версия:

Вы можете эмулировать этот процесс в два этапа:

  1. findAndModify с _id и max_value $lte значением, которое вы сейчас пытаетесь вставить. Поскольку _id является уникальным, вы знаете, что этому запросу может соответствовать только ноль или один документ - при условии, что документ с таким _id существует, он равен нулю в случае, если max_value больше, чем вы вставка, и один в случае, когда он меньше или равен. В обновлении $push новое значение и $set max_value.

  2. Если и только если шаг # 1 завершился неудачно, снова найдитеAndModify с _id и $push новым значением для массива. Поскольку шаг № 1 не выполнен, мы знаем, что текущий max_value больше нового значения, поэтому мы можем его игнорировать и просто $push новое значение.

Вот пример кода Python для реализации этого:

# the_id is the ObjectId of the document we want to modify
# new_value is the new value to append to the list
rslt1 = rslt2 = None

rslt1 = db.collection.find_and_modify(
    {'_id': the_id, 'max_value': {'$lte': new_value}},
    {'$push': {'array': new_value}, '$set': {'max_value': new_value}})

if rslt1 is None:
    rslt2 = db.collection.find_and_modify(
        {'_id': the_id},
        {'$push': {'array': new_value}})

# only one of these will be non-None; this
# picks whichever is non-None and assigns
# it to rslt
rslt = rslt1 or rslt2

(Этот оригинальный ответ работает, но обновленная версия выше более эффективна.)

Вы можете эмулировать этот процесс в три этапа:

  1. findAndModify документ с указанными _id и с max_value $gt текущим значением, которое вы пытаетесь вставить. Поскольку _id является уникальным, вы знаете, что этому запросу может соответствовать только ноль или один документ - при условии, что документ с таким _id существует, он равен нулю в случае, если max_value меньше, чем вы вставка и одна в том случае, когда она больше. Часть обновления для этого findAndModify будет $push новым значением массива.

  2. Если и только если шаг # 1 завершился неудачно, снова найдитеAndModify с _id и max_value $lte значением, которое вы сейчас пытаетесь вставить. В обновлении $push новое значение и $set max_value.

  3. В том и только в том случае, если шаг № 2 завершился неудачно, снова найдитеAndModify с _id и $push новым значением массива. Это относится к случаю, когда между шагами № 1 и № 2 другой поток повысил значение max_value до значения, превышающего значение, которое вы в настоящее время вставляете.

Вот пример кода Python для реализации этого:

# the_id is the ObjectId of the document we want to modify
# new_value is the new value to append to the list
rslt1 = rslt2 = rslt3 = None
rslt1 = db.collection.find_and_modify(
    {'_id': the_id, 'max_value': {'$gt': new_value}},
    {'$push': {'array': new_value}})

if rslt1 is None:
    rslt2 = db.collection.find_and_modify(
        {'_id': the_id, 'max_value': {'$lte': new_value}},
        {'$push': {'array': new_value}, '$set': {'max_value': new_value}})

if rslt1 is None and rslt2 is None:
    rslt3 = db.collection.find_and_modify(
        {'_id': the_id},
        {'$push': {'array': new_value}})

# only one of these will be non-None; this
# picks whichever is non-None and assigns
# it to rslt
rslt = rslt1 or rslt2 or rslt3
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...