177

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

  • 我还想记录我第一次插入它们的时间,以及最后一次在更新中看到它们的时间。
  • 我不想有重复的文件。
  • 我不想删除以前保存但不在我的更新中的文档。
  • 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/Updating

有人可以建议如何更快地做到这一点吗?

4

9 回答 9

179

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

于 2010-05-27T18:17:44.367 回答
89

从 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(
        filter={
            '_id': document['_id'],
        },
        update={
            '$setOnInsert': {
                'insertion_date': now,
            },
            '$set': {
                'last_update_date': now,
            },
        },
        upsert=True,
    )
于 2013-07-08T18:18:36.137 回答
21

您总是可以创建一个唯一索引,这会导致 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 }
于 2012-12-12T20:10:47.957 回答
15

您可以将 Upsert 与 $setOnInsert 运算符一起使用。

db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})
于 2014-09-14T15:53:40.403 回答
6

我不认为 mongodb 支持这种类型的选择性更新插入。我和 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 中指定在插入时设置哪些字段,以及在更新时保留哪些字段。

我的解决方案是在标准字段上创建一个唯一索引,执行插入,然后立即在“更新”字段上执行更新。

于 2010-07-15T21:38:21.583 回答
6

1. 使用更新。

根据上面 Van Nguyen 的回答,使用更新而不是保存。这使您可以访问 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.a. 使用 $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' }

于 2012-04-24T17:25:01.133 回答
5

概括

  • 您有一个现有的记录集合。
  • 您有一组记录,其中包含对现有记录的更新。
  • 有些更新并没有真正更新任何东西,它们复制了你已经拥有的东西。
  • 所有更新都包含已经存在的相同字段,只是可能有不同的值。
  • 您想跟踪上次更改记录的时间,以及实际更改值的位置。

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

指示:

  1. 使用 unique=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 及更低版本不包含批量更新操作。他们正在努力。

关键优化点:

  • 插入将大大加快您的批量操作。
  • 大量检索记录也会加快速度。
  • 个人更新是现在唯一可能的途径,但 10Gen 正在努力。大概,这将在 2.6 中,虽然我不确定它是否会在那时完成,但还有很多事情要做(我一直在关注他们的 Jira 系统)。
于 2013-06-09T16:55:28.370 回答
4

一般来说,在 MongoDB 中使用 update 会更好,因为如果它还不存在,它只会创建文档,尽管我不确定如何使用你的 python 适配器。

其次,如果您只需要知道该文档是否存在,则仅返回一个数字的 count() 将是比 find_one 更好的选择,后者据称会从 MongoDB 传输整个文档,从而导致不必要的流量。

于 2010-05-10T09:34:38.420 回答
1

Pymongo的方法

用于 Python 的官方 MongoDB 驱动程序

5% 的时间你可能想要更新和覆盖,而其他时候你想插入一个新行,这是通过updateOneandupsert

  • 95%(估计)的记录每天都未经修改。

以下解决方案取自这个核心 mongoDB函数:

db.collection.updateOne(filter, update, options)

根据过滤器更新集合中的单个文档。

这是通过 Pymongo 的函数完成的update_one(filter, new_values, upsert=True)

代码示例:

# importing pymongo's MongoClient
from pymongo import MongoClient
 
conn = MongoClient('localhost', 27017)
db = conn.databaseName
 
# Filter by appliances called laptops
filter = { 'user_id': '4142480', 'question_id': '2801008' }
 
# Update number of laptops to
new_values = { "$set": { 'votes': 1400 } }
 
# Using update_one() method for single update with upsert.
db.collectionName.update_one(filter, new_values, upsert=True)

做什么upsert=True

  • 如果没有文档与过滤器匹配,则创建一个新文档。
  • 更新与过滤器匹配的单个文档。
于 2022-02-15T13:54:04.750 回答