最近项目在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
资料:
- Checking if a document exists – MongoDB slow findOne vs find
- 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
来做插入操作好像速度还要更快一点🤔