web-dev-qa-db-ja.com

mongodb:存在しない場合は挿入

毎日、ドキュメントのストックを受け取ります(更新)。私がやりたいのは、まだ存在しない各アイテムを挿入することです。

  • また、最初に挿入したときと、更新で最後に表示したときも追跡します。
  • 重複したドキュメントを持ちたくない。
  • 以前に保存されたが、更新中のドキュメントを削除したくありません。
  • 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)

私の問題は、非常に遅いことです(100,000未満のレコードで40分、更新で数百万のレコードがあります)。私はこれを行うためのビルトインがかなりあると確信していますが、update()のドキュメントはmmmhhh ....少し簡潔です....( http://www.mongodb.org/display/DOCS /更新

誰かがそれをより速くする方法をアドバイスできますか?

121
LeMiz

「アップサート」をしたいようです。 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」の値のみを更新し、他のすべては変更されません。

129
Van Nguyen

MongoDB 2.4以降では、$ setOnInsertを使用できます( http://docs.mongodb.org/manual/reference/operator/setOnInsert/

Upsertコマンドで$ setOnInsertを使用して「insertion_date」を設定し、$ setを使用して「last_update_date」を設定します。

擬似コードを実用的な例に変えるには:

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

常に一意のインデックスを作成できます。これにより、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 }
15
Ram Rajamony

Upsertを$ setOnInsert演算子とともに使用できます。

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

1.更新を使用します。

上記のVan Nguyenの回答を参考にして、保存ではなく更新を使用してください。これにより、アップサートオプションにアクセスできます。

NOTE:このメソッドは、見つかったドキュメント全体を上書きします( ドキュメントから

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を使用

ドキュメント全体ではなく、選択したドキュメントを更新する場合は、updateで$ 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
Meshach Jackson

Mongodbがこの種の選択的アップサーティングをサポートしているとは思わない。 LeMizと同じ問題があり、--createdおよびupdatedの両方のタイムスタンプを処理するときにpdate(criteria、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-'name' = 'abc'、 'created' = 2010-07-12 09:09:09、および 'updated' = 2010-07の 'name' = 'abc'のドキュメントが既に存在します-13 10:10:10。アップサート後、ドキュメントはシナリオ#1の結果と同じになります。挿入する場合はどのフィールドを設定し、更新する場合はどのフィールドをそのままにするかをアップサートで指定する方法はありません。

私の解決策は、criteraフィールドに一意のインデックスを作成し、挿入を実行し、その後すぐに「updated」フィールドで更新を実行することでした。

5
Yonsink

概要

  • 既存のレコードのコレクションがあります。
  • 既存のレコードへの更新を含むセットレコードがあります。
  • 更新の一部は実際には何も更新せず、既に持っているものを複製します。
  • すべての更新には、すでに存在する同じフィールドが含まれますが、値が異なる場合があります。
  • レコードが最後に変更されたとき、値が実際に変更されたときを追跡する必要があります。

ご注意、私はPyMongoを選択しているので、選択した言語に合わせて変更してください。

手順:

  1. レコードが重複しないように、unique = trueのインデックスを使用してコレクションを作成します。

  2. 入力レコードを反復処理し、15,000レコード程度のバッチを作成します。バッチ内の各レコードについて、挿入するデータで構成される辞書を作成します。各レコードが新しいレコードになると仮定します。これらに「作成」および「更新」タイムスタンプを追加します。これを 'ContinueOnError' flag = trueのバッチ挿入コマンドとして発行します。そのため、そこに重複するキーが存在する場合でも、他のすべての挿入が発生します(存在すると思われます)。これは非常に速く起こります。バルクインサートロック、毎秒15 kのパフォーマンスレベルを取得しました。 ContinueOnErrorの詳細については、 http://docs.mongodb.org/manual/core/write-operations/ を参照してください。

    レコードの挿入は非常に高速で行われるため、それらの挿入はすぐに完了します。次に、関連するレコードを更新します。これは、一度に1つよりもはるかに高速なバッチ取得で行います。

  3. すべての入力レコードを繰り返し処理して、15K程度のバッチを作成します。キーを抽出します(キーが1つある場合に最適ですが、ない場合は仕方がありません)。 db.collectionNameBlah.find({field:{$ in:[1、2,3 ...})クエリを使用して、Mongoからこの一連のレコードを取得します。これらの各レコードについて、更新があるかどうかを判断し、ある場合は、「updated」タイムスタンプの更新を含む更新を発行します。

    残念ながら、MongoDB 2.4以前には一括更新操作は含まれていません。彼らはそれに取り組んでいます。

主要な最適化ポイント:

  • 挿入により、操作が大幅に高速化されます。
  • まとめてレコードを取得することも速度を上げます。
  • 現在、個々の更新が唯一の可能なルートですが、10Genが取り組んでいます。おそらく、これは2.6になりますが、それまでに終了するかどうかはわかりませんが、やるべきことがたくさんあります(私はJiraシステムを使用しています)。
5
Kevin J. Rice

一般に、MongoDBでupdateを使用すると、まだ存在しない場合にドキュメントを作成するだけなので、pythonアダプターでどのように動作するのかわかりませんが、より良い方法です。

第二に、そのドキュメントが存在するかどうかだけを知る必要がある場合、数字のみを返すcount()は、MongoDBからドキュメント全体を転送して不要なトラフィックを引き起こすfind_oneよりも優れたオプションになります。

4
Thomas R. Koll