这是一篇翻译文章,原连接在这里。翻译可能不许确,欢迎指出文章中存在的问题。html
PyMongo是线程安全的,而且为多线程应用提供了内置的链接池python
PyMongo不是进程安全的,若是你在fork()中使用MongoClient实例,必须当心。具体来讲,MongoClient实例不能从父进程复制到子进程,父进程和每一个子进程必须建立属于本身的MongoClient实例。因为自己的不兼容性,在子进程中使用从父进程复制的MonogoClient实例颇有可能发生死锁。PyMongo会在有可能引发死锁的状况下发出警告。git
MongoClient产生多个线程来运行后台任务,如监视链接服务器。这些线程共享受Lock实例(进程不安全)保护的状态,因此,MongoClient受到与其余使用锁(互斥)的多线程程序同样的限制,其中一个限制是在使用fork()后锁失效。在fork过程当中,全部锁都会被复制到子进程中,而且与父进程保持相同的状态:若是父进程中是锁定的,子进程复制的锁也是锁定的。由fork()建立的子进程只有一个线程,因此在这个子线程中任何从父进程中任何子线程中取出的锁都不会被释放,当这个子线程尝试获取其中一个锁时,会发生死锁。github
有关在fork()使用多线程上下文中的Python锁的问题,请参阅bugs.python.org/issue6721。web
每一个MongoClient实例在每一个MongoDb服务器都有一个内置的链接池,这些链接池会当即打开socket,用来支持多线程应用所需的并发操做MongoDB数量。这些socket没有线程相关性。mongodb
每一个链接池的大小被限制在maxPoolSize
,默认值为100.若是存在maxPoolSize
个到服务器的链接而且这些链接所有在使用中,那么到该服务器的下一个请求会一直等待,直到其中一个链接可用。shell
客户端实例在MongoDB集群中的每一个服务器上额外打开一个socket来监视服务器的状态。数据库
例如:一个链接到3个节点主从复制服务器的客户端将打开3个监视socket。它还能够根据须要打开更多个socket(最多maxPoolSize
)来支持每一个服务器上多线程应用的并发操做。在maxPoolSize
为100的状况下,若是应用只使用主链接,则只有主链接池的链接数增长(最多103)。若是应用使用ReadPreference查询辅助数据库,则它的链接池的链接数也会增长,总链接数能够达到303.django
能够经过使用minPoolSize
(默认0)参数来设置每一个服务器的最小并发链接数。链接池将初始化minPoolZise
个socket。若是因为网络缘由致使socket关闭,致使socket的链接数(使用中和空闲中)降低到最小值如下,则会打开新的socket,直到socket的数量达到最小值。json
可使用maxIdleTime
参数来设置一个链接在链接池中保持空闲的最大毫秒数,以后它将被删除或者替换,默认值为None(没有限制)。
MongoClient的默认配置适用于大多数应用:
client = MongoClient(host, port)
复制代码
为每一个进程建立一次客户端,并将其重用于全部操做。为每一个请求建立一个新的客户端是一个常见的错误,由于这样很是低效。
要在一个进程中支持极高数量的并发MongoDB操做,需增长maxPoolSize
:
client = MongoClient(host, port, maxPoolSize=200)
复制代码
或者使其没有限制:
client = MongoClient(host, port, maxPoolSize=None)
复制代码
默认状况下,容许任意数量的线程等待socket可用,而且能够等待任意长的时间。能够设置waitQueueMultiple
参数来限制等待线程的数量。例如:限制等待数量不大于500:
client = MongoClient(host, port, maxPoolSize=50, waitQueueMultiple=10)
复制代码
当已经由500个线程正在等待socket时,第501个须要socket的线程将抛出ExceededMaxWaiters。使用waitQueueMultiple
能够如今加载峰值期间应用中排队的数量,可是会引发额外的异常。
一旦链接池达到最大值,另外的线程能够无限等待socket可用,除非你设置了waitQueueTimeoutMS
:
client = MongoClient(host, port, waitQueueTimeoutMS=100)
复制代码
在这个例子中,一个线程若是等待socket的时间超过100ms,它将抛出ConnectionFailure错误。waitQueueTimeoutMS
适用于在加载峰值期间限制操做的持续时间比完成每一个操做更重要的情景。
当任何线程调用close()时, 全部闲置的socket都会被关闭,全部正在使用的socket将在它返回链接池时被关闭。
PyMongo支持CPython3.4+和PyPy3。详情请参阅Python3 FAQ。
PyMongo彻底支持Gevent。
要将MongoDB与asyncio或Tornado一块儿使用,请参阅Motor项目。
当使用insert_one()
,insert_many()
或者bulk_write()
向MongoDB中插入一个文档时,若是文档没有_id
字段,PyMongo将自动加上_id
字段,其值为ObjectId的一个实例。例如:
>>> my_doc = {'x': 1}
>>> collection.insert_one(my_doc)
<pymongo.results.InsertOneResult object at 0x7f3fc25bd640>
>>> my_doc
{'x': 1, '_id': ObjectId('560db337fba522189f171720')}
复制代码
当调用insert_many()
向单个文档插入一个引用列表时,常常会引发BulkWriteError
错误。这是几个Python习惯引发的:
>>> doc = {}
>>> collection.insert_many(doc for _ in range(10))
Traceback (most recent call last):
...
pymongo.errors.BulkWriteError: batch op errors occurred
>>> doc
{'_id': ObjectId('560f171cfba52279f0b0da0c')}
>>> docs = [{}]
>>> collection.insert_many(docs * 10)
Traceback (most recent call last):
...
pymongo.errors.BulkWriteError: batch op errors occurred
>>> docs
[{'_id': ObjectId('560f1933fba52279f0b0da0e')}]
复制代码
PyMongo以这种方式添加_id
字段有如下几个缘由:
_id
字段。_id
字段的文档,MongoDB会本身添加,而且不会返回_id
字段给PyMongo。_id
字段以前复制要插入的文档对于大多数高写入的应用而言代价是极其昂贵的。若是你不但愿PyMongo向文档中添加_id
字段,则只能插入已有_id
字段的文档。
BSON文档中的键值对能够是任何顺序(除了_id
始终是第一个)。在读写数据是,mongo shell按键保持顺序。下面的例子中请注意在插入是'b'在'a'前面,查询时也同样:
> // mongo shell.
> db.collection.insert( { "_id" : 1, "subdocument" : { "b" : 1, "a" : 1 } } )
WriteResult({ "nInserted" : 1 })
> db.collection.find()
{ "_id" : 1, "subdocument" : { "b" : 1, "a" : 1 } }
复制代码
PyMongo在默认状况下将BSON文档表示为Python字典,而且没有字典中键的顺序。也就是说,声明Python字典时,'a'在前面或者'b'在前面是同样的。
>>> print({'a': 1.0, 'b': 1.0})
{'a': 1.0, 'b': 1.0}
>>> print({'b': 1.0, 'a': 1.0})
{'a': 1.0, 'b': 1.0}
复制代码
所以,Python的字典不能保证按照他们在BSON中的顺序显示键值对。下面的例子中,'a'显示在'b'前面:
>>> print(collection.find_one())
{u'_id': 1.0, u'subdocument': {u'a': 1.0, u'b': 1.0}}
复制代码
使用SON类能够在读取BSON时保持顺序,它是一个记住了键顺序的字典。首先,获取集合的句柄,经过配置使用SON代替字典:
>>> from bson import CodecOptions, SON
>>> opts = CodecOptions(document_class=SON)
>>> opts
CodecOptions(document_class=<class 'bson.son.SON'>,
tz_aware=False,
uuid_representation=PYTHON_LEGACY,
unicode_decode_error_handler='strict',
tzinfo=None)
>>> collection_son = collection.with_options(codec_options=opts)
复制代码
如今,查询结果中的文档和副本都用SON对象表示:
>>> print(collection_son.find_one())
SON([(u'_id', 1.0), (u'subdocument', SON([(u'b', 1.0), (u'a', 1.0)]))])
复制代码
副本中键顺序与实际存储的一致:'b'在'a'前面。
因为字典中的键顺序没有定义,因此你没法预测它如何序列化到BSON。但MongoDB认为副本只有在他们的键具备相同的顺序时才是相同的。因此,使用字典查询副本可能没有结果:
>>> collection.find_one({'subdocument': {'a': 1.0, 'b': 1.0}}) is None
True
复制代码
在查询中交换键顺序没有任何区别:
>>> collection.find_one({'subdocument': {'b': 1.0, 'a': 1.0}}) is None
True
复制代码
正如咱们上面看到的,Python认为这两个字典是相同的。
由两个解决方法。第一个方法是按字段匹配副本:
>>> collection.find_one({'subdocument.a': 1.0,
... 'subdocument.b': 1.0})
{u'_id': 1.0, u'subdocument': {u'a': 1.0, u'b': 1.0}}
复制代码
上面的查询匹配任何'a'为1.0和'b'为1.0的副本,不管你在Python中指定它们的顺序如何或它们存储在BSON中的顺序如何。 此外,此查询如今能够将副本中与'a'和'b'以外的其余键相匹配,而以前的查询须要彻底匹配。
第二个方法是使用SON来指定键的顺序:
>>> query = {'subdocument': SON([('b', 1.0), ('a', 1.0)])}
>>> collection.find_one(query)
{u'_id': 1.0, u'subdocument': {u'a': 1.0, u'b': 1.0}}
复制代码
查询时,在建立SON时使用的键顺序在被序列化为BSON时会被保留。所以,您能够建立一个彻底匹配集合中的副本的副本。
更多信息,请参阅 MongoDB Manual entry on subdocument matching
若是MongoDB中的游标已经打开了很长时间而没有对它们执行任何操做,他们会在服务器上超时。这可能会致使在迭代游标时引起CursorNotFound异常。
MongoDB不支持游标自定义超时时间,但能够彻底关闭。在find()
时传入no_cursor_timeout=True
。
decimal.Decimal
实例?PyMongo >= 3.4 支持引入Decimal128 BSON类型。详情请参阅Decimal12。
MongoDB <= 3.2 仅支持IEEE 754 浮点数-与Python浮点类型相同。PyMongo能够在这些版本的MongoDB中存储Decimal实例的惟一方法是将他们转换成这个标准,因此无论如何你只能够存储浮点数。咱们强迫用户明确的作这个转换来告知它们转换正在发生。
数据库将9.99表示为IEEE浮点数(这是MongoDB和Python以及大多数其余现代语言通用的)。问题是9.99不能用双精度浮点数来表示,在Python的某些版本中也是如此:
>>> 9.99
9.9900000000000002
复制代码
使用PyMongo保存9.99时获得的结果与使用JavaScript shell或任何其余语言保存的结果彻底相同(以及将9.99输入到 Python程序的同样)。
经过
.
获取文档的值,而不只仅是如今只能用Python字典的方式获取
这个请求已经出现了不少次,但咱们决定不实现任何这样的方式。相关的jria case有关于这个决定的一些信息,这里是一个简短的总结:
有关如何正确处理datetime对象的示例,请参阅Datetime and Timezones。
PyMongo不支持保存datetime.date实例,由于BSON没有类型来保存(没有时间的日期(yyyy-MM-dd))。PyMongo并无强制将datetime.date
转换为datetime.datetime
的约定,因此你须要在代码中执行转换。
在Web应用程序中,一般在URL中会对文档的ObjectId进行编码,如:
"/posts/50b3bda58a02fb9a84d8991e"
复制代码
web框架将ObjectId做为url字符串的一部分传递给后台,所以在她们呢传递给find_one()
以前,必须转换为ObjectId。忘记这个转换是一个常见的错误。下面的例子是在Flask中正确的执行操做(其余Web框架相似):
rom pymongo import MongoClient
from bson.objectid import ObjectId
from flask import Flask, render_template
client = MongoClient()
app = Flask(__name__)
@app.route("/posts/<_id>")
def show_post(_id):
# NOTE!: converting _id from string to ObjectId before passing to find_one
post = client.db.posts.find_one({'_id': ObjectId(_id)})
return render_template('post.html', post=post)
if __name__ == "__main__":
app.run()
复制代码
更多内容,请参阅Querying By ObjectId
Django是一个流行的Python Web框架。 Django包含一个ORM,django.db。 目前,Django没有官方的MongoDB库。
django-mongodb-engine是一个非官方的MongoDB库,支持Django聚合,(原子)更新,嵌入对象,Map / Reduce和GridFS。它容许您使用Django的大部份内置功能,包括ORM,admin,authentication,site 和会话框架以及缓存。
可是,在Django中不使用Django库也很容易使用MongoDB和PyMongo。除了某些须要django.db(管理,认证和会话)的Django的功能不能使用MongoDB外,Django提供的大部分功能仍然可使用。
有一个让Django和MongoDB容易使用的项目是mango,mango是为Django会话和认证开发的一系列MongoDB库(彻底绕过django.db)。
能够。详情请参阅PyMongo and mod_wsgi
json_util是PyMongo内置的工具库,能够灵活与Python的json模块与BSON文档和 MongoDB Extended JSON一块儿使用。因为json
模块不支持一些PyMongo的特殊类型(好比ObjectId和DBRef),因此不能支持全部documents。
python-bsonjs是创建在libbson之上的将BSON快速转换为 MongoDB Extended JSON的转换器。python-bsonjs不依赖于PyMongo,能够提供比json_util更好的性能。python-bsonjs在使用RawBSONDocument时与PyMongo最适合。
PyMongo将BSON日期时间值解码为Python的datetime.datetime实例。datetime.datetime的实例被限制在datetime.MINYEAR(一般为1)和datetime.MAXYEAR(一般为9999)之间。某些MongoDB驱动程序(例如PHP驱动程序)能够存储远远超出datetime.datetime支持的年份值的BSON日期时间。
有几种方法能够解决此问题。 一种选择是过滤掉datetime.datetime支持的范围之外的值的文档:
>>> from datetime import datetime
>>> coll = client.test.dates
>>> cur = coll.find({'dt': {'$gte': datetime.min, '$lte': datetime.max}})
复制代码
另外一个方法是在你不须要日期时间字段时过滤掉这个字段:
>>> cur = coll.find({}, projection={'dt': False})
复制代码
在Unix系统上,多进程模块使用fork()
生成进程。在fork()
中使用MongoClient实例时必须当心:MongoClient的实例不能从父进程复制到子进程,父进程和每一个子进程必须建立他们本身的MongoClient实例。例如:
# Each process creates its own instance of MongoClient.
def func():
db = pymongo.MongoClient().mydb
# Do something with db.
proc = multiprocessing.Process(target=func)
proc.start()
复制代码
永远不要这样作:
client = pymongo.MongoClient()
# Each child process attempts to copy a global MongoClient
# created in the parent process. Never do this.
def func():
db = client.mydb
# Do something with db.
proc = multiprocessing.Process(target=func)
proc.start()
复制代码
因为fork()
、线程和锁之间固有的不兼容性,从父进程复制的MongoClient实例在子进程中死锁的可能性很高。