mongodb: вставить, если не существует

146

Каждый день я получаю запас документов (обновление). Что я хочу сделать, это вставить каждый элемент, который еще не существует.

  • Я также хочу отслеживать первый раз, когда я их вставил, и последний раз, когда я видел их в обновлении.
  • Я не хочу иметь дубликаты документов.
  • Я не хочу удалять документ, который был ранее сохранен, но отсутствует в моем обновлении.
  • 95% (по оценкам) записей не изменены со дня на день.

Я использую драйвер Python (pymongo).

В настоящее время я делаю (псевдокод):

for each document in update:
      existing_document = collection.find_one(document)
      if not existing_document:
           document['insertion_date'] = now
      else:
           document = existing_document
      document['last_update_date'] = now
      my_collection.save(document)

Моя проблема в том, что это очень медленно (40 минут для менее чем 100 000 записей, и у меня их миллионы в обновлении). Я почти уверен, что для этого есть что-то встроенное, но документ для update () - это ммммххх ... немного кратко .... ( http://www.mongodb.org/display/DOCS/Updating )

Может кто-нибудь посоветовать, как это сделать быстрее?

LeMiz
источник

Ответы:

153

Похоже, вы хотите сделать "upsert". MongoDB имеет встроенную поддержку для этого. Передайте дополнительный параметр вашему вызову update (): {upsert: true}. Например:

key = {'key':'value'}
data = {'key2':'value2', 'key3':'value3'};
coll.update(key, data, upsert=True); #In python upsert must be passed as a keyword argument

Это полностью заменит ваш блок if-find-else-update. Он будет вставлен, если ключ не существует, и обновится, если он существует.

Перед:

{"key":"value", "key2":"Ohai."}

После:

{"key":"value", "key2":"value2", "key3":"value3"}

Вы также можете указать, какие данные вы хотите записать:

data = {"$set":{"key2":"value2"}}

Теперь выбранный вами документ обновит значение только «key2» и оставит все остальное нетронутым.

Ван нгуен
источник
5
Это почти то, что я хочу! Как я не могу коснуться поля inserttion_date, если объект уже присутствует?
LeMiz
24
Можете ли вы привести пример установки поля при первой вставке и не обновлять его, если оно существует? @VanNguyen
Али Шакиба
7
Думаю, первая часть вашего ответа неверна. coll.update заменит данные, если вы не используете $ set. Таким образом, After будет фактически: {'key2': 'value2', 'key3': 'value3'}
Джеймс Блэкберн
9
-1 Этот ответ опасен. Вы находите по значению «ключ», а затем стираете «ключ», чтобы впоследствии вы не смогли найти его снова. Это очень маловероятный вариант использования.
Марк Э. Хааз
23
Вы должны использовать оператор $ setOnInsert! Upsert даже обновит документ, если найдет запрос.
YulCheney
64

Начиная с MongoDB 2.4, вы можете использовать $ setOnInsert ( http://docs.mongodb.org/manual/reference/operator/setOnInsert/ )

Установите 'inserttion_date', используя $ setOnInsert и 'last_update_date', используя $ set в вашей команде upsert.

Чтобы превратить ваш псевдокод в рабочий пример:

now = datetime.utcnow()
for document in update:
    collection.update_one(
        {"_id": document["_id"]},
        {
            "$setOnInsert": {"insertion_date": now},
            "$set": {"last_update_date": now},
        },
        upsert=True,
    )
Энди
источник
3
Это правильно, вы можете проверить документ, соответствующий фильтру, и вставить что-то, если не найдено, с помощью $ setOnInsert. Заметьте, однако, что была ошибка, из-за которой вы не могли $ setOnInsert с полем _id - он говорил что-то вроде «не могу изменить поле _id». Это была ошибка, исправленная в v2.5.4 или около того. Если вы видите это сообщение или проблему, просто получите последнюю версию.
Кирен Джонстон
19

Вы всегда можете создать уникальный индекс, который заставит MongoDB отклонить конфликтующее сохранение. Рассмотрим следующее с использованием оболочки mongodb:

> db.getCollection("test").insert ({a:1, b:2, c:3})
> db.getCollection("test").find()
{ "_id" : ObjectId("50c8e35adde18a44f284e7ac"), "a" : 1, "b" : 2, "c" : 3 }
> db.getCollection("test").ensureIndex ({"a" : 1}, {unique: true})
> db.getCollection("test").insert({a:2, b:12, c:13})      # This works
> db.getCollection("test").insert({a:1, b:12, c:13})      # This fails
E11000 duplicate key error index: foo.test.$a_1  dup key: { : 1.0 }
Рам Раджамони
источник
12

Вы можете использовать Upsert с оператором $ setOnInsert.

db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})
YulCheney
источник
11
Для любого, кто обращается к pymongo, третий параметр должен быть просто true или upsert = True, а не диктатом
S ..
6

1. Используйте Обновление.

Опираясь на ответ Ван Нгуена выше, используйте обновление вместо сохранения. Это дает вам доступ к опции upsert.

ПРИМЕЧАНИЕ . Этот метод переопределяет весь документ при его обнаружении ( из документов ).

var conditions = { name: 'borne' }   , update = { $inc: { visits: 1 }} , options = { multi: true };

Model.update(conditions, update, options, callback);

function callback (err, numAffected) {   // numAffected is the number of updated documents })

1.a. Использовать $ set

Если вы хотите обновить выделенный фрагмент документа, но не все, вы можете использовать метод $ set с update. (опять же из документов ) ... Итак, если вы хотите установить ...

var query = { name: 'borne' };  Model.update(query, ***{ name: 'jason borne' }***, options, callback)

Отправить как ...

Model.update(query, ***{ $set: { name: 'jason borne' }}***, options, callback)

Это помогает предотвратить случайную перезапись всех ваших документов { name: 'jason borne' }.

Мешах Джексон
источник
6

Резюме

  • У вас есть существующая коллекция записей.
  • У вас есть набор записей, которые содержат обновления существующих записей.
  • Некоторые обновления ничего не обновляют, они дублируют то, что у вас уже есть.
  • Все обновления содержат те же поля, которые уже есть, возможно, разные значения.
  • Вы хотите отслеживать, когда запись была в последний раз изменена, где значение действительно изменилось.

Обратите внимание, я предполагаю, что PyMongo, изменить в соответствии с вашим языком выбора.

Инструкции:

  1. Создайте коллекцию с индексом unique = true, чтобы вы не получали дубликаты записей.

  2. Перебирайте входные записи, создавая их из 15 000 записей или около того. Для каждой записи в пакете создайте dict, состоящий из данных, которые вы хотите вставить, предполагая, что каждая будет новой записью. Добавьте к ним «созданные» и «обновленные» временные метки. Выполните это как команду пакетной вставки с флагом 'ContinueOnError' = true, чтобы вставка всего остального происходила, даже если там есть дубликат ключа (который, как кажется, будет). ЭТО ПРОИЗОЙДЕТ ОЧЕНЬ БЫСТРО. Массовая вставка рок, я получил 15k / секунду уровней производительности. Дополнительные примечания по ContinueOnError см. По адресу http://docs.mongodb.org/manual/core/write-operations/.

    Вставка записей происходит ОЧЕНЬ быстро, так что с этими вставками вы быстро закончите. Теперь пришло время обновить соответствующие записи. Делайте это с пакетным извлечением, намного быстрее, чем по одному за раз.

  3. Повторяйте все входные записи снова, создавая пакеты по 15 КБ или около того. Извлеките ключи (лучше всего, если есть один ключ, но ничего не поделаешь, если его нет). Получите этот набор записей из Mongo с помощью запроса db.collectionNameBlah.find ({field: {$ in: [1, 2,3 ...}). Для каждой из этих записей определите, есть ли обновление, и если да, выпустите обновление, включая обновление «обновленной» временной метки.

    К сожалению, мы должны отметить, что MongoDB 2.4 и ниже НЕ включает в себя операцию массового обновления. Они работают над этим.

Ключевые точки оптимизации:

  • Вставки значительно ускорят ваши операции навалом.
  • Массовое извлечение записей также ускорит процесс.
  • Индивидуальные обновления - единственный возможный маршрут в настоящее время, но 10Gen работает над этим. Предположительно, это будет в версии 2.6, хотя я не уверен, будет ли она завершена к тому времени, есть много вещей, которые нужно сделать (я следовал их системе Jira).
Кевин Дж. Райс
источник
5

Я не думаю, что mongodb поддерживает этот тип избирательного апсайтинга. У меня та же проблема, что и у LeMiz, и использование update (критериев, newObj, upsert, multi) не работает правильно при работе с «созданной» и «обновленной» временной меткой. Учитывая следующее утверждение upsert:

update( { "name": "abc" }, 
        { $set: { "created": "2010-07-14 11:11:11", 
                  "updated": "2010-07-14 11:11:11" }},
        true, true ) 

Сценарий № 1 - документ с «именем» из «abc» не существует: новый документ создан с «name» = «abc», «create» = 2010-07-14 11:11:11 и «updated» = 2010-07-14 11:11:11.

Сценарий № 2 - документ с «именем» из «abc» уже существует со следующим: «имя» = «abc», «создан» = 2010-07-12 09:09:09 и «обновлен» = 2010-07 -13 10:10:10. После упреждения документ теперь будет таким же, как результат в сценарии № 1. В upsert нет способа указать, какие поля будут установлены при вставке и какие поля будут оставлены в одиночку при обновлении.

Моим решением было создать уникальный индекс для полей critera , выполнить вставку и сразу после этого выполнить обновление только для поля «updated».

Yonsink
источник
4

В общем, использовать обновление лучше в MongoDB, так как оно просто создаст документ, если он еще не существует, хотя я не уверен, как работать с вашим адаптером python.

Во-вторых, если вам нужно только знать, существует ли этот документ, то count (), который возвращает только число, будет лучшим вариантом, чем find_one, который предположительно передает весь документ из вашей MongoDB, вызывая ненужный трафик.

Томас Р. Колл
источник