【问题标题】:How do I conditionally upsert a document in mongo?如何有条件地在 mongo 中插入文档?
【发布时间】:2012-11-25 19:23:56
【问题描述】:

我有一份文件

{ key : 'key1', value : 'value1', update_time : 100 }

我只想更改最近(更长)的更新时间。我现在做的是:

def update_key1(new_value, new_time):
    record = find_one( { key : 'key1' } )
    if not record or record['update_time'] < new_time:
        update( { key : 'key1', value : new_value, update_time : new_time }, upsert=True)

显然这是到数据库的额外往返,但更重要的是文档上没有锁定,并发调用可能会导致数据库中剩余的 new_time 值较低。有没有办法仅在条件为真时执行 upsert?

编辑:只是为了澄清,目的不是为每个键创建多个文档,然后在查找时进行排序。虽然这样可以解决我的问题,但这些值会发生很大变化并且会浪费很多空间。

【问题讨论】:

标签: mongodb pymongo


【解决方案1】:

如果您在key 上添加unique constraint,那么带有update_time 过滤器和upsert=TrueupdateOne

  1. 插入缺失的记录
  2. 更新陈旧的记录
  3. 尝试使用stale input进行更新时抛出错误(因为更新将不匹配过滤条件,并且由于约束而插入失败)
collection.updateOne({'key': key,
                      'update_time': {'$lt': new_time}
                      },
                     {'$set': {'value': new_value,
                               'update_time': new_time
                               }
                      },
                     upsert=True
                     )

您可以捕获错误并检查code: 11000(违反唯一约束),以专门处理那些试图更新到过去状态的情况。

【讨论】:

  • 如果您可以在相关键上设置唯一性,则效果很好。
【解决方案2】:

无法以原子方式完成整个事情,有两种现有条件需要进行更改,并且可以原子方式处理它们中的每一种:

  • 不存在密钥记录
  • 存在密钥记录,其update_time 早于new_time

更新密钥的现有记录:

def update_if_stale(key, new_value, new_time):
    collection.update({'key': key,
                       'update_time': {'$lt': new_time}
                       },
                      {'$set': {'value': new_value,
                                'update_time': new_time
                                }
                       }
                      )

如果之前不存在 key 的记录,则插入:

def insert_if_missing(key, new_value, new_time):
    collection.update({'key': key},
                      {'$setOnInsert': {'value': new_value,
                                        'update_time': new_time
                                        }
                       },
                      upsert=True
                      )

$setOnInsert 在 MongoDB 2.4 中添加)

你也许可以把这些放在一起得到你需要的东西,例如:

def update_key(key, new_value, new_time):
    insert_if_missing(key, new_value, new_time)        
    update_if_stale(key, new_value, new_time)

但是,根据您的系统中可能的删除/插入时间尺度,您可能需要多次调用(更新/插入/更新)或其他恶作剧。

另外:如果您希望将缺少update_time 字段的记录视为过时记录进行更新,请将{'$lt': new_time}} 更改为{'$not': {'$gte': new_time}}

【讨论】:

    【解决方案3】:

    如果 WPCoder 对 and upsert=True 的响应不是您想要的,那么您可能需要 $setOnInsert,它尚未实现:https://jira.mongodb.org/browse/SERVER-340。它应该在 2.4.0 中实现。

    【讨论】:

      【解决方案4】:

      您可以进行conditional 更新:

      db.MyObjects.update( { key : "key1", value: { $lt : aValue }},
                          { $set : { value : aValue }});
      

      【讨论】:

      • 但是如果对象不存在,我想插入它。如果我将其更改为 upsert,如果它不是最新的,它会添加一个新文档,对吧?
      • 是的,如果您的查询是正确的,它应该可以这样工作。确保提供了您的 _id 列,否则它将创建一个新文档。
      • @WiredPrairie 进行 upsert 的原因是因为您不知道是否存在匹配的文档;你怎么知道它要提供的_id?
      • 您不需要将ObjectId 用于_id
      • @WiredPrairie 您的建议有效,但会导致“重复密钥错误收集”。有没有更清洁的方法来做到这一点?
      【解决方案5】:

      看来您正在寻找findAndModify。抱歉,我对 python 不熟悉,所以我无法给你一个代码 sn-p 但命令应该很简单。

      【讨论】:

      • 我认为 findAndModify 不会解决这个问题。问题源于确定是否对记录执行修改。我从文档中看不到这样做的方法。
      猜你喜欢
      • 2014-08-20
      • 1970-01-01
      • 2011-05-21
      • 1970-01-01
      • 2016-05-15
      • 2021-03-28
      • 1970-01-01
      相关资源
      最近更新 更多