mongodb:如果不存在则插入

时间:2010-05-10 07:33:32

标签: python mongodb bulkinsert mongodb-query

每天,我收到一份文件(更新)。我想要做的是插入每个尚不存在的项目。

  • 我还想跟踪我第一次插入它们,以及最后一次在更新中看到它们。
  • 我不想要有重复的文件。
  • 我不想删除以前保存过的文档,但不在我的更新中。
  • 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分钟,而且我在更新中有数百万条记录)。 我很确定有内置的东西可以做到这一点,但更新()的文件是mmmhhh ....有点简洁....(http://www.mongodb.org/display/DOCS/Updating

有人可以建议如何更快地完成它吗?

8 个答案:

答案 0 :(得分:122)

听起来你想做一个“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”的值,并保持其他所有内容不变。

答案 1 :(得分:49)

从MongoDB 2.4开始,你可以使用$ setOnInsert(http://docs.mongodb.org/manual/reference/operator/setOnInsert/

使用$ setOnInsert设置'insertion_date',使用upsert命令中的$ 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,
    )

答案 2 :(得分:15)

您总是可以创建一个唯一索引,这会导致MongoDB拒绝冲突的保存。考虑使用mongodb shell完成以下操作:

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

答案 3 :(得分:11)

您可以在$ setOnInsert运算符中使用Upsert。

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

答案 4 :(得分:6)

1。使用更新。

根据Van Nguyen的回答,使用更新而不是保存。这使您可以访问upsert选项。

注意:此方法会在找到时覆盖整个文档(From the docs

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。 (再次,From the docs)...... 所以,如果你想设置......

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 :(得分:5)

我认为mongodb不支持这种类型的选择性upserting。我遇到与LeMiz相同的问题,并且在处理“已创建”和“已更新”时间戳时,使用 update(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 - 'name'为'abc'的文档不存在: 使用'name'='abc'创建新文档,'created'= 2010-07-14 11:11:11,'updated'= 2010-07-14 11:11:11。

场景#2 - 'name'为'abc'的文档已经存在,包含以下内容: 'name'='abc','created'= 2010-07-12 09:09:09,'updated'= 2010-07-13 10:10:10。 在upsert之后,文档现在将与场景#1中的结果相同。如果插入,则无法在upsert中指定要设置哪些字段,以及在更新时保留哪些字段。

我的解决方案是在 critera 字段上创建一个唯一索引,执行插入操作,然后立即在'updated'字段上执行更新。

答案 6 :(得分:5)

<强>摘要

  • 您有一个现有的记录集。
  • 您有一组包含现有记录更新的记录。
  • 有些更新并没有真正更新任何内容,它们会复制您已经拥有的内容。
  • 所有更新都包含已存在的相同字段,可能只有不同的值。
  • 您想要跟踪上次更改记录的时间,实际更改的值。

注意,我假设PyMongo,改为适合您选择的语言。

<强>说明:

  1. 使用唯一= true的索引创建集合,这样您就不会获得重复记录。

  2. 迭代输入记录,创建大约15,000条记录的批次。对于批处理中的每个记录,创建一个由要插入的数据组成的字典,假设每个记录都是新记录。将“已创建”和“已更新”时间戳添加到这些时间戳。将此问题作为带有'ContinueOnError'标志= true的批量插入命令发出,因此即使其中存在重复键(它听起来会有),也会发生其他所有内容的插入。这将非常快。批量插入摇滚,我获得了15k /秒的性能水平。关于ContinueOnError的进一步说明,请参阅http://docs.mongodb.org/manual/core/write-operations/

    记录插入非常快,因此您可以立即完成这些插入操作。现在,是时候更新相关记录了。通过批量检索执行此操作,比一次快一个。

  3. 再次迭代所有输入记录,创建15K左右的批次。提取密钥(如果有一个密钥,则最好,但如果没有,则无法帮助)。使用db.collectionNameBlah.find({field:{$ in:[1,2,3 ...})查询从Mongo检索这一组记录。对于每个记录,确定是否有更新,如果是,则发出更新,包括更新“更新的”时间戳。

    不幸的是,我们应该注意,MongoDB 2.4及更低版本不包含批量更新操作。他们正在努力。

  4. 主要优化点:

    • 插件将大大加快您的操作速度。
    • 整体检索记录也会加快速度。
    • 个人更新现在是唯一可行的路线,但10Gen正在研究它。据推测,这将是2.6,但我不确定它是否会在那时完成,还有很多事要做(我一直关注他们的Jira系统)。

答案 7 :(得分:4)

一般情况下,在MongoDB中使用更新更好,因为如果它还不存在,它只会创建文档,虽然我不知道如何使用你的python适配器。

其次,如果您只需要知道该文档是否存在,那么只返回一个数字的count()将是一个比find_one更好的选择,因为它可能会从MongoDB中传输整个文档,导致不必要的流量。