Mongodb: einfügen, wenn nicht vorhanden

Jeden Tag bekomme ich einen Vorrat an Dokumenten (ein Update). Was ich tun möchte, ist, jedes Element einzufügen, das noch nicht existiert.

  • Ich möchte auch das erste Mal, wie ich sie eingefügt habe, behalten und das letzte Mal sah ich sie in einem Update.
  • Ich möchte keine doppelten Dokumente haben.
  • Ich möchte kein Dokument entfernen, das zuvor gespeichert wurde, aber nicht in meinem Update ist.
  • 95% (geschätzt) der Aufzeichnungen sind von Tag zu Tag unverändert.

Ich benutze den Python-Fahrer (Pymongo).

Was ich derzeit mache (Pseudocode):

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) 

Mein Problem ist, dass es sehr langsam ist (40 Minuten für weniger als 100 000 Datensätze, und ich habe Millionen von ihnen im Update). Ich bin mir ziemlich sicher, dass es hier etwas gebaut hat, aber das Dokument für Update () ist mmmhhh …. ein bisschen knapp … ( http://www.mongodb.org/display/DOCS/Updating )

Kann jemand raten, wie man es schneller macht?

    8 Solutions collect form web for “Mongodb: einfügen, wenn nicht vorhanden”

    Klingt wie du willst ein "upsert" machen. MongoDB hat dafür Unterstützung. Übergeben Sie einen zusätzlichen Parameter an Ihren Update () Aufruf: {upsert: true}. Beispielsweise:

     key = {'key':'value'} data = {'key2':'value2', 'key3':'value3'}; coll.update(key, data, {upsert:true}); 

    Dies ersetzt den if-find-else-update-Block vollständig. Es wird eingefügt, wenn der Schlüssel nicht existiert und aktualisiert wird, wenn dies der Fall ist.

    Vor:

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

    Nach:

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

    Sie können auch festlegen, welche Daten Sie schreiben möchten:

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

    Jetzt wird Ihr ausgewähltes Dokument den Wert von "key2" nur aktualisieren und alles andere unberührt lassen.

    Ab MongoDB 2.4 können Sie $ setOnInsert ( http://docs.mongodb.org/manual/reference/operator/setOnInsert/ ) verwenden,

    Setzen Sie 'insertion_date' mit $ setOnInsert und 'last_update_date' mit $ set in Ihrem upsert Befehl.

    Um Ihren Pseudocode zu einem funktionierenden Beispiel zu machen:

     now = datetime.utcnow() for document in update: collection.update_one( {"_id": document["_id"]}, { "$setOnInsert": {"insertion_date": now}, "$set": {"last_update_date": now}, }, upsert=True, ) 

    Du könntest immer einen eindeutigen Index machen, der dazu führt, dass MongoDB ein widersprüchliches Speichern ablehnt. Betrachten Sie das folgende mit der Mongodb-Schale:

     > 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 } 

    Sie können Upsert mit $ setOnInsert-Operator verwenden.

     db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true}) 

    1. Verwenden Sie Update.

    Zeichnung von Van Nguyens Antwort oben, verwenden Sie Update statt speichern. Hier haben Sie Zugriff auf die Upsert-Option.

    HINWEIS : Diese Methode überschreibt das gesamte Dokument bei der Suche ( aus den Dokumenten )

     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 Verwenden Sie $ set

    Wenn Sie eine Auswahl des Dokuments aktualisieren möchten, aber nicht die ganze Sache, können Sie die $ set Methode mit Update verwenden. (Wieder, aus den docs ) … Also, wenn du willst …

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

    Sende es als …

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

    Dies hilft, versehentlich überschreiben alle Ihre Dokumente mit { name: 'jason borne' } .

    Ich glaube nicht, dass Mongodb diese Art von selektivem Aufstoßen unterstützt. Ich habe das gleiche Problem wie LeMiz, und mit Update (Kriterien, newObj, upsert, multi) funktioniert nicht richtig, wenn es um einen "erstellt" und "aktualisierten" Zeitstempel geht. Angesichts der folgenden upsert-Anweisung:

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

    Scenario # 1 – Dokument mit 'name' von 'abc' existiert nicht: Neues Dokument wird mit 'name' = 'abc', 'created' = 2010-07-14 11:11:11 und 'aktualisiert' = erstellt 2010-07-14 11:11:11

    Scenario # 2 – Dokument mit 'name' von 'abc' existiert bereits mit folgendem 'name' = 'abc', 'created' = 2010-07-12 09:09:09 und 'aktualisiert' = 2010-07 -13 10:10:10. Nach dem upsert wäre das Dokument nun das gleiche wie das Ergebnis im Szenario # 1. Es gibt keine Möglichkeit, in einem upsert anzugeben, welche Felder beim Einfügen gesetzt werden sollen und welche Felder bei der Aktualisierung alleine gelassen werden.

    Meine Lösung war es, einen eindeutigen Index auf den Kriterienfeldern zu erstellen, einen Insert auszuführen und sofort ein Update nur auf dem 'aktualisierten' Feld auszuführen.

    Im Allgemeinen ist das Update in MongoDB besser, da es nur das Dokument erstellen wird, wenn es noch nicht existiert, obwohl ich nicht sicher bin, wie ich das mit deinem Python-Adapter arbeiten kannst.

    Zweitens, wenn Sie nur wissen müssen, ob dieses Dokument existiert oder nicht, ist count (), die nur eine Zahl zurückgibt, eine bessere Option als find_one, die angeblich das gesamte Dokument von Ihrem MongoDB überträgt und unnötigen Verkehr verursacht.

    Zusammenfassung

    • Sie haben eine vorhandene Datensammlung.
    • Sie haben Datensätze, die Aktualisierungen der vorhandenen Datensätze enthalten.
    • Einige der Updates nicht wirklich alles aktualisieren, sie duplizieren, was Sie bereits haben.
    • Alle Updates enthalten die gleichen Felder, die schon vorhanden sind, ggf. verschiedene Werte.
    • Sie wollen verfolgen, wann ein Datensatz zuletzt geändert wurde, wo sich ein Wert tatsächlich geändert hat.

    Beachten Sie, ich vermute PyMongo, ändern Sie, um Ihrer Sprache der Wahl zu entsprechen.

    Anleitung:

    1. Erstellen Sie die Sammlung mit einem Index mit unique = true, so dass Sie keine doppelten Datensätze erhalten.

    2. Iterate über Ihre Eingabedatensätze, die Schaffung von Chargen von ihnen von 15.000 Datensätzen oder so. Für jeden Datensatz im Batch, erstellen Sie ein Dict, bestehend aus den Daten, die Sie einfügen möchten, vorausgesetzt, jeder wird ein neuer Datensatz sein. Fügen Sie die "erstellten" und "aktualisierten" Zeitstempel hinzu. Geben Sie dies als Batch-Insert-Befehl mit dem 'ContinueOnError' Flag = true, so dass die Einfügung von allem anderen passiert, auch wenn es dort einen doppelten Schlüssel gibt (was es klingt wie es sein wird). DAS WIRD SEHR SCHNELL. Bulk-Inserts Rock, ich habe 15k / Sekunde Performance Levels bekommen. Weitere Hinweise zu ContinueOnError finden Sie unter http://docs.mongodb.org/manual/core/write-operations/

      Rekord-Inserts passieren sehr schnell, so dass Sie mit diesen Einsätzen in kürzester Zeit fertig werden. Jetzt ist es Zeit, die relevanten Aufzeichnungen zu aktualisieren. Tun Sie dies mit einem Batch-Retrieval, viel schneller als ein zu einer Zeit.

    3. Iterate über alle Ihre Eingabedatensätze wieder, so dass Batches von 15K oder so. Auszug aus den Schlüsseln (am besten, wenn es eine Taste, aber kann nicht geholfen werden, wenn es nicht). Holt diesen Bündel von Aufzeichnungen aus dem Mongo mit einem db.collectionNameBlah.find ({field: {$ in: [1, 2,3 …}) Abfrage. Für jede dieser Datensätze, bestimmen, ob es ein Update gibt, und wenn ja, geben Sie das Update, einschließlich der Aktualisierung der 'aktualisierten' Zeitstempel.

      Leider sollten wir beachten, dass MongoDB 2.4 und unten keine Massenaktualisierungsoperation enthalten. Sie arbeiten daran.

    Schlüsseloptimierungspunkte:

    • Die Einsätze beschleunigen Ihren Betrieb in loser Schüttung.
    • Abrufen von Aufzeichnungen en masse wird auch die Dinge beschleunigen.
    • Einzelne Updates sind die einzige mögliche Route jetzt, aber 10Gen arbeitet daran. Vermutlich wird dies in 2.6 sein, obwohl ich nicht sicher bin ob es bis dahin fertig sein wird, da ist eine Menge Sachen zu tun (ich habe ihr Jira System verfolgt).
    Python ist die beste Programmiersprache der Welt.