在实际使用mongodb的场景中,我们经常遇到多个请求同时在某个collection里update多条document的需求。这个需求看似有许多种解法,但是具体哪种好也说不准。现在便让我们一探究竟吧~

首先我们利用pymongo添加1000000条数据,name字段为hello:

from pymongo import MongoClient
client = MongoClient()
db = client['test']
coll = db['concurrency']
coll.insert_many([
        "name": "hello",
        "num": i
    for i in range(1000000)

然后,我们另外加一个进程,启动任务为将num字段为偶数的documents的name字段给update成aa,而主线程则update所有documents的name字段为bb。代码如下:

from pymongo import MongoClient
import pprint
from multiprocessing import Pool, Process
client = MongoClient()
db = client['test']
coll = db['concurrency']
def f(name, m):
    coll.update_many(
        {"num": {"$mod": [m, 0]}},
        {"$set": {"name": str(name)}},
    return 1
if __name__ == '__main__':
    p = Process(target=f, args=('aa', 2))
    p.start()
    coll.update_many({}, {"$set": {"name": 'bb'}})
    p.join()
    docs = coll.find()
    d = dict()
    for doc in docs:
        n = doc['name']
        if n not in d.keys():
            d[n] = 0
        d[n] += 1
    pprint.pprint(d)

最后结果是:

{'aa': 9809, 'bb': 990191}

可以看到,两个conn提交的update_many请求,在mongo内部并不是原子的任务。进程比主线程后起,因此进程在update时,主线程已经执行了部分update任务了。但是由于进程update的documents数量较少,因此很快就追上了主线程的进度,从而只有约10000个record最后是被进程给update的。

在mongo官方的写操作原子性文档中提到,mongodb对于单个document的写操作是原子的。也就是说,在updateMany里,对每一个符合filter的document的修改操作是原子的,但是整个updateMany,不会阻塞其它client的update操作。

如果要多个updateMany任务不发生并发,最简便的第一种方法是在业务逻辑中加锁,或者用一个任务队列进行管理。这种方法不仅适合updateMany场景,同样也适合在update的时候,需要修改表结构的场景(document全量update,可能需要先delete后insert)。

第二种方法是利用mongodb提供的事务功能,使得在单个session上这些updateMany操作能够有序进行。事务功能启用需要mongo为副本集模式,版本至少4.0,若有分布式事务的需求需要至少4.2的版本。

第三种方法则是在每一个documents中加入version字段,在update的filter中去另外对比version版本号,从而保证最新版本的documents能够存入数据库。在这个思路下,也可以另外再加一个meta表存储最新version的信息(每一个doc里还是要version字段)。只有成功更新了meta表的version,才能updateMany数据表中的documents,否则阻止这个意向。

第三种方法相对前两种方法可控性较低,因此实际场景中,暂推荐第一种以及第二种方法。(如果有其它更有效的方法,欢迎指正orz)

在第一种方法的基础上延伸,可以设置一定量的lock,而后将请求信息hash掉,mod进特定idx的lock。这样只需要控制lock的量,就能控制多个updateMany的需求了。

在实际使用mongodb的场景中,我们经常遇到多个请求同时在某个collection里update多条document的需求。这个需求看似有许多种解法,但是具体哪种好也说不准。现在便让我们一探究竟吧~首先我们利用pymongo添加1000000条数据,name字段为hello:from pymongo import MongoClientclient = MongoClient()db ...
线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误、警告、及用户行为等信息,通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题,但当产生大量的日志之后,要想从大量日志里挖掘出有价值的内容,则需要对数据进行进一步的存储和分析。本文以存储web服务的访问日志为例,介绍如何使用MongoDB来存储、分析日志数据,让日志数据发挥最大的价值,本文的内容同样使用其他的日志存储型应用。 一个典型的web服务器的访问日志类似如下,包含访问来源、用户、访问的资源地址、访问结果、用户使用的系统及浏览器类型等
myclient = pymongo.MongoClient(host='localhost',port=27017) mydb = myclient["du_brand"] mycol = mydb["content"] mycol2 = mydb["content_new"] f1 = mycol.find({"color":{"$exists":True}...
MongoTemplate批量更新(支持泛型) MongoTemplate所有批量操作中,批量添加是最简单的,直接使用mongoTemplate.insert()即可。 // MongoTemplate.class源码中insert方法有支持集合参数的重载函数,并且已经支持泛型 public <T> Collection<T> insert(Collection<? extends T> batchToSave, Class<?> entityClass) 语法是:Article.updateMany({ 查询条件},{要修改的值}) //这里我的实例代码为 Article.updateMany({ username: 123},{username:yuchu} 这里是以username属性为例,当查询条件成立后,修改的属性也可以是ti
集合(collection) 文档(document) 在MongoDB中,数据库和集合都不需要手动创建,当我们创建文档,如果文档所在的集合或数据库不存在会自动创建数据库和集合 当我们向集合中插入文档,如果没有给文档指定_id属性,则数据库会自动为文档添加_id,该属性用来作为文档的唯一标识 显示当前的所有数据库show dbs 或 show databases 进入到指定的数据库中use 数据
可以使用 $push 操作符来向嵌套文档中添加一条数据。例如,假设我们有一个名为 "users" 的集合,其中每个文档都包含一个名为 "name" 的字段和一个名为 "addresses" 的嵌套文档数组。要向 "addresses" 数组中添加一条新的地址,可以使用以下命令: db.users.update( { name: "John" }, { $push: { addresses: { street: "123 Main St", city: "Anytown", state: "CA", zip: "12345" } } } 这将向名为 "John" 的用户文档的 "addresses" 数组中添加一条新的地址。