mongodb: вставить, если не существует - PullRequest
114 голосов
/ 10 мая 2010

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

  • Я также хочу отслеживать первый раз, когда я их вставил, и последний раз, когда я видел их в обновлении.
  • Я не хочу иметь дубликаты документов.
  • Я не хочу удалять документ, который был ранее сохранен, но отсутствует в моем обновлении.
  • 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)

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

Ответы [ 8 ]

124 голосов
/ 27 мая 2010

Звучит так, будто ты хочешь сделать "упертость". 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» и оставит все остальное без изменений.

52 голосов
/ 08 июля 2013

Начиная с 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,
    )
15 голосов
/ 13 декабря 2012

Вы всегда можете создать уникальный индекс, который заставит 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 }
11 голосов
/ 14 сентября 2014

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

db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})
6 голосов
/ 24 апреля 2012

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.а. Используйте $ 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' }.

5 голосов
/ 09 июня 2013

Резюме

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

Обратите внимание, я предполагаю, что 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 голосов
/ 16 июля 2010

Я не думаю, что 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» не существует: Новый документ создается с именем «=» abc, «создан» = 2010-07-14 11:11:11 и «обновлен» = 2010-07-14 11: 11: 11.

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

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

4 голосов
/ 10 мая 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...