万物之中, 希望至美.

MongoDB查询写入优化

2019.10.11

最近项目在MongoDB写入数据这块有瓶颈,以此记录一下优化过程中的技巧。

查询

索引

首先,最简单粗暴的就是加索引,这没什么问题吧(逃

检查一个文档是否存在: find().limit(1) vs find_one()

最开始我是使用 find_one() 来判断一个文档是否存在,然后查了下资料发现 find_one 实际上是使用了 find().limit(N) ,然后取一条,在这个过程中,find_one 会读取所有匹配条件的文档;而 find().limit(1) 只会读取 1 条匹配条件的文档,这里就是他们的差距所在。PyMongo 的源码:

    def find_one(self, filter=None, *args, **kwargs):
        """
        省略注释
        """
        if (filter is not None and not
                isinstance(filter, abc.Mapping)):
            filter = {"_id": filter}

        cursor = self.find(filter, *args, **kwargs)
        for result in cursor.limit(-1):
            return result
        return None

资料:

  1. Checking if a document exists – MongoDB slow findOne vs find
  2. Difference between MongoDB’s find and findone calls

写入

使用批量插入代替单次插入

for ip, port in addr.items():
    db.collection.insert_one({'ip': ip, 'port': port})

这样写在数据量小的时候没什么问题,但数据量大了之后,会异常的慢,因为每次插入都是一次网络 I/O。

此时需要使用批量插入的方式:

db.collection.insert_many(({'ip': ip, 'port': port} for ip, port in addr.items()))

调整 insert_many 的参数

其中还可以通过 insert_many 的参数来进一步优化插入的性能。

def insert_many(self, documents, ordered=True,
                bypass_document_validation=False, session=None)
  • ordered: 这个参数为 True 时,迫使 MongoDB 按顺序同步插入数据;而如果为 False,则 MongoDB 会并发的不按固定顺序进行批量插入。显然当我们对性能有要求时,将该参数设为False是非常必要的。
  • bypass_document_validation: MongoDB 3.2 之后加入了 document validation 功能,用于验证写入的文档是否符合 collection 制定的规则。而既然是验证就肯定需要花费时间,当我们对性能有极致要求时,也可以将此参数设为True,从而越过验证,直接写入。具体可查看该链接:Document Validation
  • session: 关于 session, 还请移步 Client Sessions and Causal Consistency Guarantees

批量更新

前面的例子在插入操作时非常有效,但是对于更新操作由于 update_many 无法针对每一个 doc 进行更新,如本例中针对每一个 ip 进行更新,那么就需要使用 bulk_write 操作。

from pymongo import UpdateOne

db.collection.bulk_write(
    [UpdateOne({'ip': ip}, {'$set': {'port': port}}, upsert=True)
     for ip, port in addr.items()], ordered=False,
    bypass_document_validation=True
)

PS: 在我的主观感受下,使用 bulk_write 来做插入操作好像速度还要更快一点🤔

comments powered by Disqus