在pymongo快速或批量Upsert

时间:2011-03-13 21:04:25

标签: python mongodb pymongo upsert nosql

如何在pymongo中进行批量upsert?我想更新一堆条目,一次只做一个条目非常慢。

几乎完全相同的问题的答案在这里:Bulk update/upsert in MongoDB?

接受的答案实际上并没有回答这个问题。它只是提供了一个指向mongo CLI的链接,用于执行导入/导出。

我也愿意向某人解释为什么做一个批量upsert是不可能/没有最佳做法,但请解释这类问题的首选解决方案是什么。

6 个答案:

答案 0 :(得分:30)

MongoDB 2.6+支持批量操作。这包括批量插入,升级,更新等。这样做的目的是减少/消除执行逐个记录操作的往返延迟的延迟('文档按文档'正确)

那么,这是如何工作的? Python中的示例,因为那是我正在做的工作。

>>> import pymongo
>>> pymongo.version
'2.7rc0'

要使用此功能,我们会创建一个'批量'对象,向其添加文档,然后在其上调用execute,它将立即发送所有更新。注意事项:收集的操作的BSONsize(bsonsizes的总和)不能超过16 MB的文档大小限制。当然,操作次数因此可能会有很大差异,您的里程可能会有所不同。

批量upsert操作的Pymongo示例:

import pymongo
conn = pymongo.MongoClient('myserver', 8839)
db = conn['mydbname']
coll = db.myCollection
bulkop = coll.initialize_ordered_bulk_op()
retval = bulkop.find({'field1':1}).upsert().update({'$push':{'vals':1}})
retval = bulkop.find({'field1':1}).upsert().update({'$push':{'vals':2}})
retval = bulkop.find({'field1':1}).upsert().update({'$push':{'vals':3}})
retval = bulkop.execute()

这是必不可少的方法。更多信息请访问:

http://api.mongodb.org/python/2.7rc1/examples/bulk.html

编辑: - 从python驱动程序的3.5版开始,不推荐使用initialize_ordered_bulk_op。请改用bulk_write()。 [http://api.mongodb.com/python/current/api/pymongo/collection.html#pymongo.collection.Collection.bulk_write]

答案 1 :(得分:26)

现代版本的pymongo(大于3.x)将批量操作包装在一致的界面中,降级服务器版本不支持批量操作的地方。现在,这在MongoDB官方支持的驱动程序中是一致的。

因此,首选的编码方法是使用bulk_write(),而使用UpdateOne其他适当的操作操作。现在,当然最好使用自然语言列表而不是特定的构建器

旧文件的直接翻译:

from pymongo import UpdateOne

operations = [
    UpdateOne({ "field1": 1},{ "$push": { "vals": 1 } },upsert=True),
    UpdateOne({ "field1": 1},{ "$push": { "vals": 2 } },upsert=True),
    UpdateOne({ "field1": 1},{ "$push": { "vals": 3 } },upsert=True)
]

result = collection.bulk_write(operations)

或经典文档转换循环:

import random
from pymongo import UpdateOne

random.seed()

operations = []

for doc in collection.find():
    # Set a random number on every document update
    operations.append(
        UpdateOne({ "_id": doc["_id"] },{ "$set": { "random": random.randint(0,10) } })
    )

    # Send once every 1000 in batch
    if ( len(operations) == 1000 ):
        collection.bulk_write(operations,ordered=False)
        operations = []

if ( len(operations) > 0 ):
    collection.bulk_write(operations,ordered=False)

返回的结果为BulkWriteResult,其中包含匹配和更新文档的计数器以及发生的任何“upserts”的返回_id值。

对批量操作数组的大小存在一些误解。发送到服务器的实际请求不能超过16MB BSON限制,因为该限制也适用于发送到使用BSON格式的服务器的“请求”。

但是,这不会影响您可以构建的请求数组的大小,因为实际操作只会以1000的批量发送和处理。唯一真正的限制是这1000条操作指令本身实际上并不创建大于16MB的BSON文档。这确实是一个非常高的命令。

批量方法的一般概念是“流量较少”,这是因为一次发送许多内容并且只处理一个服务器响应。减少每个更新请求所附带的开销可以节省大量时间。

答案 2 :(得分:5)

答案保持不变:不支持批量upsert。

答案 3 :(得分:1)

您可以使用multi = True更新符合查询规范的所有文档。

关于以您希望的方式执行一批命令,存在错误here

答案 4 :(得分:0)

使用Python 3.5 +,motor和asyncio进行最快的批量更新:

import asyncio
import datetime
import logging
import random
import time

import motor.motor_asyncio
import pymongo.errors


async def execute_bulk(bulk):
    try:
        await bulk.execute()
    except pymongo.errors.BulkWriteError as err:
        logging.error(err.details)


async def main():
    cnt = 0
    bulk = db.initialize_unordered_bulk_op()
    tasks = []
    async for document in db.find({}, {}, no_cursor_timeout=True):
        cnt += 1
        bulk.find({'_id': document['_id']}).update({'$set': {"random": random.randint(0,10)}})
        if not cnt % 1000:
            task = asyncio.ensure_future(execute_bulk(bulk))
            tasks.append(task)
            bulk = db.initialize_unordered_bulk_op()
    if cnt % 1000:
        task = asyncio.ensure_future(bulk.execute(bulk))
        tasks.append(task)
    logging.info('%s processed', cnt)
    await asyncio.gather(*tasks)


logging.basicConfig(level='INFO')    
db = motor.motor_asyncio.AsyncIOMotorClient()['database']['collection']
start_time = time.time()
loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
finally:
    execution_time = time.time() - start_time
    logging.info('Execution time: %s', datetime.timedelta(seconds=execution_time))

答案 5 :(得分:0)

如果您有很多数据,并且您希望使用“_id”来判断数据是否存在,

你可以试试......

import pymongo
from pymongo import UpdateOne
client = pymongo.MongoClient('localhost', 27017)
db=client['sampleDB']

collectionInfo = db.sample

#sample data
datas=[
    {"_id":123456,"name":"aaa","N":1,"comment":"first sample","lat":22,"lng":33},
    {"_id":234567,"name":"aaa","N":1,"comment":"second sample","lat":22,"lng":33},
    {"_id":345678,"name":"aaa","N":1,"comment":"xxx sample","lat":22,"lng":33},
    {"_id":456789,"name":"aaa","N":1,"comment":"yyy sample","lat":22,"lng":33},
    {"_id":123456,"name":"aaaaaaaaaaaaaaaaaa","N":1,"comment":"zzz sample","lat":22,"lng":33},
    {"_id":11111111,"name":"aaa","N":1,"comment":"zzz sample","lat":22,"lng":33}
]

#you should split judge item and other data 
ids=[data.pop("_id") for data in datas]

operations=[UpdateOne({"_id":idn},{'$set':data},upsert=True) for idn ,data in zip(ids,datas)]

collectionInfo.bulk_write(operations)

我的英语很差,如果你不明白我说的话,我很抱歉