天天看點

【極客日常】解決使用mongodb時同時update多條資料的問題

在實際使用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的需求了。