常见问题#

PyMongo线程安全吗?#

PyMongo是线程安全的,为线程应用程序提供了内置的连接池。

PyMongo fork安全吗?#

PyMongo不安全。在使用 MongoClient 具有 fork() . 具体来说,MongoClient的实例不能从父进程复制到子进程。相反,父进程和每个子进程必须创建自己的MongoClient实例。从父进程复制的MongoClient实例在子进程中很可能出现死锁,因为它们之间固有的不兼容 fork() ,描述了线程和锁 below . 如果有可能发生这种死锁,PyMongo将尝试发出警告。

MongoClient生成多个线程来运行后台任务,例如监视连接的服务器。这些线程共享受的实例保护的状态 Lock ,它们本身就是 not fork-safe . 因此,驱动程序受到与使用的任何其他多线程代码相同的限制 Lock (以及一般的互斥体)。这些限制之一是锁在 fork() . 在fork期间,所有锁都以与父进程相同的状态复制到子进程:如果它们被锁定,则复制的锁也将被锁定。创建的孩子 fork() 只有一个线程,因此任何被父线程中的其他线程取出的锁都不会在子线程中被释放。下次子进程试图获取其中一个锁时,就会发生死锁。

从4.3版开始,PyMongo使用 os.register_at_fork() 事件后重置子进程中的锁定和其他共享状态 os.fork() 以减少死锁的频率。但是,死锁仍然是可能的,因为PyMongo依赖的库,如 OpenSSLgetaddrinfo(3) (在某些平台上),在多线程应用程序中是不安全的。Linux还对以下内容施加了限制:

在.之后 fork() 在多线程程序中,子级只能安全地调用异步信号安全函数(请参见 signal-safety(7) )直到它调用的时间 execve(2)

PyMongo依赖于以下函数 not async-signal-safe 因此,子进程在尝试调用非 async-signal-safe 功能。有关可能发生的死锁或崩溃的示例,请参见 PYTHON-3406

关于多线程上下文中Python锁的问题 fork() ,参见http://bugs.python.org/issue6721。

作为Pandas,PyMongo可以帮助我加载查询结果吗 DataFrame#

虽然PyMongo本身不提供任何用于处理数值或列数据的API, PyMongoArrow 是一个与PyMongo配套的库,它可以轻松地将MongoDB查询结果集加载为 Pandas DataFramesNumPy ndarrays ,或 Apache Arrow Tables

连接池在PyMongo中是如何工作的?#

每个 MongoClient 在您的MongoDB拓扑中,每个服务器都有一个内置的连接池。这些池按需打开套接字,以支持多线程应用程序所需的并发MongoDB操作数。套接字没有线程关联。

每个连接池的大小限制为 maxPoolSize ,默认为100。如果有 maxPoolSize 与服务器的连接和所有连接都在使用中,对该服务器的下一个请求将等到其中一个连接可用为止。

客户端实例在MongoDB拓扑中为每个服务器打开两个额外的套接字,用于监视服务器的状态。

例如,连接到3节点副本集的客户端打开6个监视套接字。它还根据需要打开尽可能多的套接字,以支持每个服务器上的多线程应用程序的并发操作,最多 maxPoolSize 。使用一个 maxPoolSize 为100时,如果应用程序仅使用主连接池(默认设置),则只有主连接池会增长,并且总连接数最多为106。如果应用程序使用 ReadPreference 为了查询辅助数据库,它们的池也会增长,总连接数可以达到306个。

此外,池的速率受到限制,因此每个连接池在任何时候最多只能并行创建2个连接。连接创建涵盖了建立新连接所需的所有工作,包括DNS、TCP、SSL/TLS、MongoDB握手和MongoDB身份验证。例如,如果三个线程同时尝试从空池中签出连接,则前两个线程将开始创建新连接,而第三个线程将等待。第三个线程在以下任一情况下停止等待:

  • 前两个线程中的一个完成创建连接,或者

  • 现有连接将签回池中。

速率限制并发连接创建降低了连接风暴的可能性,并提高了驱动程序重用现有连接的能力。

可以使用设置到每个服务器的最小并发连接数 minPoolSize ,默认为0。将使用此数量的套接字初始化连接池。如果由于任何网络错误而关闭套接字,导致套接字总数(正在使用和空闲)低于最小值,则会打开更多的套接字,直到达到最小值。

可以使用以下参数设置连接在被删除和替换之前可以在池中保持空闲的最大毫秒数 maxIdleTimeMS ,它缺省为 None (无限制)。

的默认配置 MongoClient 适用于大多数应用:

client = MongoClient(host, port)

创建此客户端 once 对于每个流程,并在所有操作中重用它。为每个请求创建一个新的客户端是一个常见的错误,这是非常低效的。

要在一个进程中支持极高数量的并发MongoDB操作,请增加 maxPoolSize ::

client = MongoClient(host, port, maxPoolSize=200)

... 或者让它无界:

client = MongoClient(host, port, maxPoolSize=None)

一旦池达到其最大大小,其他线程必须等待套接字可用。PyMongo不限制可以等待套接字可用的线程数,应用程序有责任在负载峰值期间将其线程池的大小限制为绑定队列。线程可以等待任何时间长度,除非 waitQueueTimeoutMS 定义为:

client = MongoClient(host, port, waitQueueTimeoutMS=100)

等待套接字超过100ms(在本例中)的线程将引发 ConnectionFailure . 如果在负载峰值期间绑定操作持续时间比完成每个操作更重要,请使用此选项。

什么时候? close() 被任何线程调用时,所有空闲套接字都将关闭,并且所有正在使用的套接字在返回池时都将被关闭。

PyMongo支持python3吗?#

PyMongo支持CPython3.7+和PYP3.8+。请参阅 Python 3常见问题解答 了解更多细节。

PyMongo支持Gevent、asyncio、Tornado或Twisted等异步框架吗?#

PyMongo完全支持 Gevent .

使用MongoDB asyncioTornadoMotor 项目。

为了 TwistedTxMongo . 它宣称的任务是保持与PyMongo相同的特性。

为什么PyMongo要在我的所有文档中添加一个u id字段?#

当使用 insert_one()insert_many()bulk_write() ,并且该文档不包括 _id 字段,PyMongo会自动为您添加一个,设置为 ObjectId . 例如::

>>> my_doc = {'x': 1}
>>> collection.insert_one(my_doc)
InsertOneResult(ObjectId('560db337fba522189f171720'), acknowledged=True)
>>> 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 以这种方式显示的原因如下:

  • 所有MongoDB文档都需要有一个 _id 字段。

  • 如果PyMongo插入一个没有 _id 但Pyodb不会给Mongo增加价值。

  • 在添加之前复制要插入的文档 _id 对于大多数高写入量应用程序来说,field的成本将高得令人望而却步。

如果你不想PyMongo添加 _id 在文档中,仅插入已具有 _id 字段,由应用程序添加。

子文档中的键顺序——为什么我的查询在shell中工作而PyMongo不工作?#

BSON文档中的键值对可以有任何顺序(除了 _id 总是第一个)。mongoshell在读写数据时保持键顺序。当我们创建文档和显示文档时,请注意“b”在“a”之前:

> // mongo shell.
> db.collection.insertOne( { "_id" : 1, "subdocument" : { "b" : 1, "a" : 1 } } )
WriteResult({ "nInserted" : 1 })
> db.collection.findOne()
{ "_id" : 1, "subdocument" : { "b" : 1, "a" : 1 } }

默认情况下,PyMongo将BSON文档表示为Python dict,并且dict中的键顺序没有定义。也就是说,对于Python来说,先用“a”键声明的dict与先用“b”键声明的dict是相同的:

>>> 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 dict不能保证按照它们在BSON中的存储顺序显示键。这里,“a”在“b”之前:

>>> print(collection.find_one())
{'_id': 1.0, 'subdocument': {'a': 1.0, 'b': 1.0}}

要在读取BSON时保持顺序,请使用 SON 类,它是一个能够记住其键顺序的dict。首先,获取集合的句柄,配置为使用 SON 代替dict:

>>> from bson import CodecOptions, SON
>>> opts = CodecOptions(document_class=SON)
>>> opts
CodecOptions(document_class=...SON..., tz_aware=False, uuid_representation=UuidRepresentation.UNSPECIFIED, unicode_decode_error_handler='strict', tzinfo=None, type_registry=TypeRegistry(type_codecs=[], fallback_encoder=None), datetime_conversion=DatetimeConversion.DATETIME)
>>> collection_son = collection.with_options(codec_options=opts)

现在,查询结果中的文档和子文档用 SON 物体:

>>> print(collection_son.find_one())
SON([('_id', 1.0), ('subdocument', SON([('b', 1.0), ('a', 1.0)]))])

子文档的实际存储布局现在可见:“b”在“a”之前。

因为dict的键顺序没有定义,所以无法预测它将如何序列化 to 布森。但是MongoDB认为只有当子文档的键具有相同的顺序时,子文档才是相等的。因此,如果使用dict查询子文档,它可能不匹配:

>>> 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认为这两个dict是相同的。

有两种解决方案。首先,可以按字段匹配子文档字段:

>>> collection.find_one({'subdocument.a': 1.0,
...                      'subdocument.b': 1.0})
{'_id': 1.0, 'subdocument': {'a': 1.0, 'b': 1.0}}

查询将匹配“a”为1.0、a“b”为1.0的任何子文档,而不管您在Python中指定它们的顺序或它们在BSON中的存储顺序。此外,此查询现在使用“a”和“b”之外的其他键匹配子文档,而上一个查询需要完全匹配。

第二种解决方案是使用 SON 要指定密钥顺序:

>>> query = {'subdocument': SON([('b', 1.0), ('a', 1.0)])}
>>> collection.find_one(query)
{'_id': 1.0, 'subdocument': {'a': 1.0, 'b': 1.0}}

在创建 SON 在序列化为BSON并用作查询时保留。因此,您可以创建与集合中的子文档完全匹配的子文档。

参见

MongoDB Manual entry on subdocument matching <https://mongodb.com/docs/manual/tutorial/query-embedded-documents/> _.

什么? CursorNotFound 游标id在服务器上无效意味着?#

如果MongoDB中的游标已经打开很长时间而没有对其执行任何操作,那么它们可能会在服务器上超时。这会导致 CursorNotFound 尝试迭代游标时引发异常。

如何更改游标的超时值?#

不能完全关闭游标mongouts,但不能完全关闭游标。通过 no_cursor_timeout=Truefind() .

我怎么储存 decimal.Decimal 实例?#

PyMongo>=3.4支持MongoDB 3.4中引入的Decimal128 BSON类型。看到了吗 decimal128 更多信息。

MongoDB<=3.2只支持ieee754浮点-与Python float类型相同。PyMongo将Decimal实例存储到这些版本的MongoDB中的唯一方法是将它们转换成这个标准,因此您实际上只存储float—我们强制用户显式地进行这种转换,以便他们知道它正在发生。

我在存钱 9.99 但是当我查询我的文档包含 9.9900000000000002 -这是怎么回事?#

数据库表示是 9.99 作为一个IEEE浮点(这是MongoDB和Python以及大多数其他现代语言所共有的)。问题是 9.99 不能用双精度浮点精确表示-在某些版本的Python中也是如此:

>>> 9.99
9.9900000000000002

你存钱时得到的结果 9.99 使用PyMongo的结果与JavaScript shell或任何其他语言保存的结果完全相同(并且作为输入时使用的数据) 9.99 进入Python程序)。

你能为文档添加属性样式访问吗?#

这个请求已经出现了很多次,但是我们决定不实现类似的东西。相关的 jira case 有一些关于决策的信息,但这里是一个简短的总结:

  1. 这将污染文档的属性命名空间,因此在使用与dictionary方法同名的键时,可能会导致细微的错误/令人困惑的错误。

  2. 我们甚至使用子对象而不是常规字典的唯一原因是为了维护密钥顺序,因为服务器在某些操作中需要这样做。因此,我们对不必要地使SON复杂化感到犹豫不决(在某些情况下,我们可能希望恢复到单独使用字典的状态,而不会破坏对每个人的向后兼容性)。

  3. 对于新用户来说,处理文档很容易(而且是Pythonic),因为它们的行为就像字典一样。如果我们开始改变他们的行为,这就给新用户增加了进入的障碍——另一个需要学习的类。

用PyMongo处理时区的正确方法是什么?#

日期时间和时区 有关如何处理的示例 datetime 对象正确。

如何保存 datetime.date 实例?#

PyMongo不支持储蓄 datetime.date 实例,因为没有时间的日期没有BSON类型。而不是让驱动程序强制执行转换的约定 datetime.date 实例到 datetime.datetime 对于您来说,任何转换都应该在您的客户端代码中执行。

当我在web应用程序中按ObjectId查询文档时,我没有得到任何结果#

在web应用程序中,将文档的objectid编码为url是很常见的,比如:

"/posts/50b3bda58a02fb9a84d8991e"

您的web框架将把URL的ObjectId部分作为字符串传递给您的请求处理程序,因此必须将其转换为 ObjectId 在传递给 find_one() . 忘记做这种转换是一个常见的错误。以下是如何在 Flask (其他web框架类似):

from 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()

如何使用Django的PyMongo?#

Django 是一个流行的Python web框架。Django包含一个ORM, django.db . 目前,Django没有官方的MongoDB后端。

django-mongodb-engine 是一个非官方的MongoDB后端,支持Django聚合、(原子)更新、嵌入式对象、Map/Reduce和GridFS。它允许您使用Django的大多数内置特性,包括ORM、管理、身份验证、站点和会话框架以及缓存。

但是,从Django使用MongoDB(和PyMongo)很容易,而不需要使用Django后端。Django的某些特性需要 django.db (管理、身份验证和会话)仅使用MongoDB是行不通的,但是Django提供的大部分功能仍然可以使用。

有一个项目应该使使用MongoDB和Django更容易 mango . Mango是一组MongoDB后端,用于Django会话和身份验证(绕过 django.db 完全)。

PyMongo和 mod_wsgi 是吗?#

对。请参阅配置指南 PyMongo和modwsgi .

PyMongo和PythonAnywhere一起工作吗?#

不,PyMongo创建的Python线程 PythonAnywhere 不支持。有关详细信息,请参阅 PYTHON-1495 .

我怎样才能使用像Python这样的东西 json 模块将我的文档编码为JSON?#

json_util 是PyMongo内置的、灵活的工具,用于使用 json 包含BSON文档和 MongoDB Extended JSON 。这个 json 由于PyMongo支持某些特殊类型(如 ObjectIdDBRef ),这在JSON中不支持。

python-bsonjs 是一个快速的BSON到MongoDB扩展JSON转换器,它构建在 libbson . python-bsonjs 不依赖PyMongo,可以提供很好的性能改进 json_util . python-bsonjs 使用时与PyMongo配合使用效果最佳 RawBSONDocument .

为什么我会得到另一种语言的驱动程序存储的解码日期溢出错误?#

PyMongo将BSON datetime值解码为Python的 datetime.datetime . 实例 datetime.datetime 限制在年之间 datetime.MINYEAR (通常为1)和 datetime.MAXYEAR (通常为9999)。一些MongoDB驱动程序(例如PHP驱动程序)可以存储BSON日期时间,其年值远远超出了 datetime.datetime .

有几种方法可以解决此问题。从PyMongo 4.3开始, bson.decode() 可以用四种方法之一对bson日期时间进行解码,并且可以使用 datetime_conversion 的参数 CodecOptions

默认选项为 DATETIME ,它将尝试解码为 datetime.datetime ,允许 OverflowError 发生在超出范围的日期时。 DATETIME_AUTO 更改此行为以改为返回 DatetimeMS 当表示超出范围时,返回 datetime 与以前一样的对象:

>>> from datetime import datetime
>>> from bson.datetime_ms import DatetimeMS
>>> from bson.codec_options import DatetimeConversion
>>> from pymongo import MongoClient
>>> client = MongoClient(datetime_conversion=DatetimeConversion.DATETIME_AUTO)
>>> client.db.collection.insert_one({"x": datetime(1970, 1, 1)})
InsertOneResult(ObjectId('...'), acknowledged=True)
>>> client.db.collection.insert_one({"x": DatetimeMS(2**62)})
InsertOneResult(ObjectId('...'), acknowledged=True)
>>> for x in client.db.collection.find():
...     print(x)
...
{'_id': ObjectId('...'), 'x': datetime.datetime(1970, 1, 1, 0, 0)}
{'_id': ObjectId('...'), 'x': DatetimeMS(4611686018427387904)}

有关其他选择,请参阅 DatetimeConversion

另一个不涉及设置的选项 datetime_conversion 用于筛选出支持的范围之外的文档值 datetime

>>> from datetime import datetime
>>> coll = client.test.dates
>>> cur = coll.find({'dt': {'$gte': datetime.min, '$lte': datetime.max}})

另一个选项,假设您不需要datetime字段,则只过滤掉该字段:

>>> cur = coll.find({}, projection={'dt': False})

在多处理中使用PyMongo#

在Unix系统上,多处理模块使用 fork() . 在使用 MongoClient 具有 fork() . 具体来说,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()

从父进程复制的MongoClient实例在子进程中很可能出现死锁,原因是 inherent incompatibilities between fork(), threads, and locks . 如果有可能发生这种死锁,PyMongo将尝试发出警告。