mongoDB 具有多个键值的 `upsert`

mongoDB `upsert` with multiple key values

我正在从 Amazon Mechanical Turk 中提取一些数据并将其保存在 mongodb 集合中。

我让多名工人重复每项任务,因为一点冗余有助于我检查工作质量。

每次我使用 boto AWS python interface 从亚马逊提取数据时,我都会获得一个包含所有已完成 HIT 的文件,并想将它们插入到集合中。

这是我要插入到 collection 中的 document:

    mongo_doc = \
    {'subj_id'    :data['subj_id'],
    'img_id'      :trial['img_id'],
    'data_list'   :trial['data_list'],
    'worker_id'   :worker_id,
    'worker_exp'  :worker_exp,
    'assignment_id':ass_id
    }

使用 boto 的连续拉取将包含相同的数据,但我不想在我的集合中有重复的文档。

我知道有两种可能的解决方案,但 none 完全适合我:

  1. 我可以在集合中搜索该文档,仅当不存在时才将其插入。但这会产生非常高的计算成本。

  2. 我可以使用 upsert 作为一种方法来确保仅当某个键尚未包含时才插入文档。但是所有包含的键都可以复制,因为该任务由多个工作人员重复。

第 2 部分注意: - subj_idimg_iddata_list 可以重复,因为不同的工作人员注释相同的主题、图像并且可以提供相同的数据。 - worker_idworker_expassignment_id 可以重复,因为工作人员在同一个任务中注释多个图像。 - 唯一独特之处在于所有这些字段的组合。

有没有一种方法可以插入 mongo_doc 只有在之前没有插入的情况下?

只要"all"这里你想做的是"insert"项那么你这里有几个选择:

  1. 在所有必填字段中创建一个 "unique" 索引并使用插入。简单地说,当值的组合与已经存在的东西相同时,将抛出 "duplicate key" 错误。这会阻止同一事物被添加两次,并且可以提醒您出现异常。这可能最好与 Bulk Operations API 和 "unordered" 标志一起用于操作。相同的 "unordered" 可用于 insert_many(),但我个人更喜欢 Bulk API 的语法,因为它允许更好的构建和混合操作:

    bulk = pymongo.bulk.BulkOperationBuilder(collection,ordered=False)
    bulk.insert(document)
    result = bulk.execute()
    

    如果在调用 .execute() 之前使用了多个操作,那么所有操作都会立即发送到服务器并且只有 "one" 响应。使用 "unordered",无论 "duplicate" 键等错误如何,所有项目都会被处理,并且 "result" 包含任何失败项目的报告。

    这里明显的 "cost" 是在所有字段上创建 "unique" 索引将使用相当多的 space 并增加显着的开销 "write"作为索引信息的操作必须像数据一样被写入。

  2. 将 "upsert" 功能与 $setOnInsert 结合使用。这允许您使用 "all required unique fields" 构造查询,以便 "search" 查找文档是否存在。标准 "upsert" 行为是在找不到文档的地方创建 "new" 文档。

    $setOnInsert 添加的内容是,该语句中的所有字段 "set" 仅在出现 "upsert" 的地方应用。在常规 "match" 中,$setOnInsert 中的所有赋值都将被忽略:

    bulk = pymongo.bulk.BulkOperationBuilder(collection,ordered=True)
    bulk.find({ 
        "subj_id": data["subj_id"], 
        "img_id": data["img_id"] 
        "data_list": data["data_list"],
        "worker_id": data["worker_id"], 
        "worker_exp": data["worker_exp"], 
        "assignment_id": data["assignment_id"]
    }).upsert().update_one({
        "$setOnInsert": {
            # Just the "insert" fields or just "data" as an object for all
            "subj_id": data["subj_id"], 
            "img_id": data["img_id"] 
            "data_list": data["data_list"],
            "worker_id": data["worker_id"], 
            "worker_exp": data["worker_exp"], 
            "assignment_id": data["assignment_id"]
        },
        "$set": {
            # Any other fields "if" you want to update on match
        }
    })
    result = bulk.execute()
    

    根据您的需要,如果文档匹配,您可以使用 $set 或其他运算符来更新您 "want" 的任何内容,或者完全忽略它,只有 "inserts" 会在不匹配的地方出现。

    不能做的当然是做一些事情,比如将1的值赋给$setOnInsert中的一个字段,然后做一些事情,比如$inc 在其他操作上。这会在您尝试修改 "same path" 时产生冲突,并会引发错误。

    在那种情况下,最好保留 $setOnInsert 块的 $inc 字段 "out",让它正常运行。 { "$inc": 1 } 只会在第一次提交时分配 1$push 和其他运算符也是如此。

    "cost" 再次分配索引,"need" 不是 "unique" 但可能应该是。如果没有索引,操作是 "scanning the collection" 以获得可能的匹配而不是更有效的索引。所以它不是"required",但在未指定索引的情况下,额外"writes"的成本通常超过"lookup"的成本。

与 "Bulk" 操作相结合的进一步优势在于,由于 $setOnInsert 的 "upsert" 方法在所有唯一键都在查询,这可以与 "ordered" 一起用于批处理,如所演示的那样。

当 "ordered" 在一批操作中,操作在添加的 "sequence" 中处理,所以如果 "first" 插入发生对你很重要committed 然后它是 preffferable "unordered",虽然并行执行速度更快,但当然不能保证按照它们被构造的相同顺序提交操作。

无论哪种方式,您都需要花费维护 "unique" 项目的多个键的任一种形式。可能要查看 "reduce" 索引成本的替代方法是查看将文档的 _id 字段替换为您考虑的所有值 "unique".

因为主键总是 "unique" 并且总是 "required" 这最小化了写 "additional indexes" 的 "cost" 并且可能是一个可以考虑的选项。 _id 不会 "need" 成为 ObjectId,并且由于它可以是复合对象,因此如果您有另一个唯一标识符,那么以这种方式使用它可能是明智的,避免进一步的唯一重复。