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 записів, і в мене є оновлення мільйонів). Я впевнений, що для цього є щось вбудоване, але документ для оновлення () є mmmhhh .... трохи коротко .... ( http://www.mongodb.org/display/DOCS/Updating )

Хтось може порадити, як це зробити швидше?

Відповіді:


153

Здається, що ви хочете зробити «прихистку». 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
Це майже те, що я хочу! Як я не можу торкнутися поля insert_date, якщо об'єкт вже присутній?
LeMiz

24
чи можете ви надати приклад просто встановлення поля на першій вставці та не оновлювати його, якщо воно існує? @VanNguyen
Алі Шакіба

7
Перша частина вашої відповіді неправильна, я думаю. coll.update замінить дані, якщо ви не використовуєте $ set. Тож Після насправді буде: {'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})


11
Для тих, хто запитує про пімонго, третій парам повинен бути просто істинним чи верхнім = True, а не диктатом
S ..

6

1. Використовуйте оновлення.

Спираючись на відповідь Ван Нгуєна вище, використовуйте оновлення замість збереження. Це надає вам доступ до опціону.

ПРИМІТКА . Цей спосіб переосмислює весь документ, коли його знайдено ( З документів )

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 з оновленням. (знову ж таки, із Документів ) ... Отже, якщо ви хочете встановити ...

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. Створіть колекцію з індексом унікальним = true, щоб не отримати повторюваних записів.

  2. Повторіть свої вхідні записи, створивши з них партії з 15000 записів або близько того. Для кожного запису в пакеті створіть дикт, що складається з даних, які ви хочете вставити, припускаючи, що кожен з них буде новим записом. Додайте до них «створені» та «оновлені» часові позначки. Випустіть це як команду пакетної вставки із прапорцем «ContinueOnError» = true, тому вставлення всього іншого відбувається, навіть якщо там є дублікат ключа (який, здається, буде). ЦЕ ЩО БУДЕ ДУЖЕ ДУЖЕ. Об'ємні вставки рок, я отримав 15k / second рівнів продуктивності. Подальші примітки щодо ContinueOnError див. На http://docs.mongodb.org/manual/core/write-operations/

    Вставки записів трапляються ДУЖЕ швидко, тому з цими вставками ви закінчитеся в найкоротші терміни. Тепер прийшов час оновити відповідні записи. Робіть це за допомогою пакетного пошуку, набагато швидшого, ніж один за одним.

  3. Знову повторюйте всі ваші вхідні записи, створюючи партії 15 Кб або близько того. Витягніть ключі (найкраще, якщо є одна клавіша, але неможливо допомогти, якщо її немає). Отримайте цю купу записів з Монго за допомогою запиту db.collectionNameBlah.find ({field: {$ in: [1, 2,3 ...}). Для кожного з цих записів визначте, чи є оновлення, і якщо так, опублікуйте оновлення, включаючи оновлення часової позначки "оновлений".

    На жаль, слід зазначити, що MongoDB 2.4 і нижче не включають операцію масового оновлення. Вони над цим працюють.

Основні точки оптимізації:

  • Вставки значно прискорять ваші операції оптом.
  • Масове завантаження записів теж прискорить справи.
  • Індивідуальні оновлення - єдиний можливий маршрут зараз, але 10Gen працює над цим. Імовірно, це буде в 2.6, хоча я не впевнений, чи закінчиться він до цього часу, є багато чого робити (я слідкував за їх системою Джира).

5

Я не думаю, що mongodb підтримує цей тип вибіркових дозволів. У мене така ж проблема, як у LeMiz, і використання оновлення (критерії, newObj, upsert, multi) не працює правильно, коли стосується як "створеної", так і "оновленої" часової позначки. З огляду на наступне твердження:

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

Сценарій №1 - документа з 'ім'ям' abc 'не існує: новий документ створюється з' name '=' abc ',' created '= 2010-07-14 11:11:11, і' updated '= 2010-07-14 11:11:11.

Сценарій №2 - документ із 'ім'ям' abc 'вже існує з таким:' name '=' abc ',' created '= 2010-07-12 09:09:09 та' updated '= 2010-07 -13 10:10:10. Після перерви документ зараз буде таким же, як і результат у сценарії №1. У вкладці немає способу вказати, які поля слід встановити при вставлянні, а які поля залишити в спокої при оновленні.

Моє рішення полягало в тому, щоб створити унікальний індекс на полях критера , виконати вставку та одразу після цього виконати оновлення просто в полі 'оновлений'.


4

Загалом, використання оновлення краще в MongoDB, оскільки воно просто створить документ, якщо він ще не існує, хоча я не впевнений, як це працювати з вашим адаптером python.

По-друге, якщо вам потрібно лише знати, чи існує цей документ чи ні, count (), який повертає лише число, буде кращим варіантом, ніж find_one, який нібито передає весь документ з вашого MongoDB, викликаючи непотрібний трафік.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.