文档菜单
文档首页
/ / /
PyMongo

故障排除

在本页

  • 连接
  • 读写操作
  • 游标
  • 投影
  • 索引
  • 数据格式
  • TLS
  • 客户端操作超时
  • 进程复制

在本页,您可以找到使用PyMongo与MongoDB时遇到的常见问题的解决方案。

如果您尝试连接到MongoDB服务器v3.4或更早版本,PyMongo可能会引发以下错误

pymongo.errors.ConfigurationError: Server at localhost:27017 reports wire version 5, but this version of PyMongo requires at least 6 (MongoDB 3.6).

这种情况发生在驱动程序版本对于它要连接的服务器来说太新的时候。要解决这个问题,请将您的MongoDB部署升级到v3.6或更高版本,或者降级到支持MongoDB服务器v2.6及更高版本的PyMongo v3.x。

一个自动重连异常表明发生了故障转移。这意味着PyMongo已失去了与副本集原始主成员的连接,并且其最后操作可能已失败。

当发生此错误时,PyMongo会自动尝试为后续操作找到新的主成员。为了处理此错误,您的应用程序必须采取以下操作之一

  • 重试可能已失败的操作

  • 继续运行,同时理解操作可能已失败

重要

PyMongo在副本集选择新的主成员之前,对所有操作都会引发自动重连错误。

如果您尝试通过 SSH 隧道连接到 MongoDB 副本集,您将收到以下错误

File "/Library/Python/2.7/site-packages/pymongo/collection.py", line 1560, in count
return self._count(cmd, collation, session)
File "/Library/Python/2.7/site-packages/pymongo/collection.py", line 1504, in _count
with self._socket_for_reads() as (connection, slave_ok):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py", line 17, in __enter__
return self.gen.next()
File "/Library/Python/2.7/site-packages/pymongo/mongo_client.py", line 982, in _socket_for_reads
server = topology.select_server(read_preference)
File "/Library/Python/2.7/site-packages/pymongo/topology.py", line 224, in select_server
address))
File "/Library/Python/2.7/site-packages/pymongo/topology.py", line 183, in select_servers
selector, server_timeout, address)
File "/Library/Python/2.7/site-packages/pymongo/topology.py", line 199, in _select_servers_loop
self._error_message(selector))
pymongo.errors.ServerSelectionTimeoutError: localhost:27017: timed out

这是因为 PyMongo 通过 isMaster 命令的响应来发现副本集成员,该响应包含其他副本集成员的地址和端口号。然而,您无法通过 SSH 隧道访问这些地址和端口号。

相反,您可以通过使用带有 SSH 隧道的 directConnection=True 选项直接连接到单个 MongoDB 节点。

如果您在读取优先级中指定了 tag-sets,而 MongoDB 无法找到具有指定标记的副本集成员,您将收到此错误。为了避免此错误,在标记集列表末尾包括一个空字典({})。这指示 PyMongo 在找不到匹配的标记时从任何匹配读取参考模式的成员中进行读取。

PyMongo 不再支持 count() 方法。相反,请使用来自 Collection 类的 count_documents() 方法。

重要

count_documents() 方法属于 Collection 类。如果您尝试调用 Cursor.count_documents(),PyMongo 将引发以下错误

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Cursor' object has no attribute 'count'

提供无效的关键字参数名称会导致驱动程序引发此错误。

请确保您指定的关键字参数存在且拼写正确。

在 Web 应用程序中,通常会将文档的 ObjectId 编码到 URL 中,如下面的代码示例所示

"/posts/50b3bda58a02fb9a84d8991e"

您的 Web 框架将 URL 中的 ObjectId 部分作为字符串传递给您的请求处理器。在将其传递给 find_one() 方法之前,您必须将字符串转换为 ObjectId 实例。

以下代码示例展示了如何在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()

在始终位于首位的_id字段之后,BSON文档中的键值对可以以任何顺序出现。在读取和写入数据时,mongo shell保留键的顺序,如下面的代码示例中字段“b”和“a”所示

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

PyMongo默认将BSON文档表示为Python字典,字典中键的顺序未定义。在Python中,以“a”键首先声明的字典与以“b”键首先声明的字典相同。在以下示例中,无论键在print语句中的顺序如何,都显示了相同的键顺序

print({'a': 1.0, 'b': 1.0})
# Returns: {'a': 1.0, 'b': 1.0}
print({'b': 1.0, 'a': 1.0})
# Returns: {'a': 1.0, 'b': 1.0}

同样,Python字典可能不会按其在BSON中存储的顺序显示键。以下示例显示了先前示例中插入的文档的打印结果

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

要保留读取BSON时键的顺序,请使用SON类,这是一个记得其键顺序的字典。

以下代码示例展示了如何创建一个配置为使用SON类的集合

from bson import CodecOptions, SON
opts = CodecOptions(document_class=SON)
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”之前。

由于Python字典的键顺序未定义,因此您无法预测它将如何序列化为BSON。但是,MongoDB仅当子文档的键顺序相同时才认为它们相等。如果您使用Python字典查询子文档,它可能不会匹配

collection.find_one({'subdocument': {'b': 1.0, 'a': 1.0}}) is None
True

因为Python认为这两个字典相同,所以在查询中交换键的顺序没有任何区别

collection.find_one({'subdocument': {'b': 1.0, 'a': 1.0}}) is None
True

您可以通过两种方式解决这个问题。首先,您可以逐字段匹配子文档

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

查询匹配任何具有“a”为1.0和“b”为1.0的子文档,无论您在Python中指定它们的顺序如何,或者它们在BSON中存储的顺序如何。此查询现在还匹配除了“a”和“b”之外的其他键的子文档,而之前的查询则需要精确匹配。

第二种解决方案是使用一个~bson.son.SON对象来指定键顺序

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

驱动程序在将~bson.son.SON序列化为BSON并用作查询时,保留您在创建时使用的键顺序。因此,您可以创建一个与集合中子文档完全匹配的子文档。

注意

有关子文档匹配的更多信息,请参阅MongoDB服务器文档中的查询嵌入式/嵌套文档指南。

PyMongo v3.8或更早版本,如果您向Cursor构造函数提供无效参数,则会引发一个TypeError和一个AttributeError。这个AttributeError是无关紧要的,但TypeError包含了如下示例所示的调试信息

Exception ignored in: <function Cursor.__del__ at 0x1048129d8>
...
AttributeError: 'Cursor' object has no attribute '_Cursor__killed'
...
TypeError: __init__() got an unexpected keyword argument '<argument>'

要修复此问题,请确保您提供正确的关键字参数。您还可以升级到PyMongo v3.9或更高版本,这将消除无关的错误。

MongoDB 中的 cursor 在服务器上可能超时,如果它们长时间打开且没有执行任何操作。当您尝试遍历 cursor 时,这可能导致 CursorNotFound 异常。

如果尝试在单个投影中包含和排除字段,驱动程序会返回带有此消息的 OperationFailure。确保您的投影仅指定要包含的字段或要排除的字段。

如果你执行了一个写入操作,该操作存储了违反唯一索引的重复值,驱动程序将引发一个 DuplicateKeyException,MongoDB 抛出类似于以下错误的错误

E11000 duplicate key error index

此错误是由于尝试将本地 UUID 对象编码为 Binary 对象,而 UUID 表示形式为 UNSPECIFIED 时产生的,如下所示

unspecified_collection.insert_one({'_id': 'bar', 'uuid': uuid4()})
Traceback (most recent call last):
...
ValueError: cannot encode native uuid.UUID with UuidRepresentation.UNSPECIFIED.
UUIDs can be manually converted to bson.Binary instances using bson.Binary.from_uuid()
or a different UuidRepresentation can be configured. See the documentation for
UuidRepresentation for more information.

相反,您必须使用 Binary.from_uuid() 方法显式地将本地 UUID 转换为 Binary 对象,如下所示

explicit_binary = Binary.from_uuid(uuid4(), UuidRepresentation.STANDARD)
unspec_collection.insert_one({'_id': 'bar', 'uuid': explicit_binary})

PyMongo 将 BSON datetime 值解码为 Python 的 datetime.datetime 类的实例。datetime.datetime 的实例限制在 datetime.MINYEAR (1) 和 datetime.MAXYEAR (9999) 之间。一些 MongoDB 驱动器可以将 BSON 日期时间存储为 datetime.datetime 支持的范围之外的年份值。

有几种方法可以解决这个问题。从 PyMongo 4.3 版本开始,bson.decode 可以以四种方式之一解码 BSON datetime 值。您可以通过使用 ~bson.codec_options.CodecOptionsdatetime_conversion 参数来指定转换方法。

默认转换选项是 ~bson.codec_options.DatetimeConversion.DATETIME,它将尝试将值解码为 datetime.datetime,允许对于超出范围的日期发生 ~builtin.OverflowError~bson.codec_options.DatetimeConversion.DATETIME_AUTO 改变了这种行为,在表示超出范围时返回 ~bson.datetime_ms.DatetimeMS,而以前返回 ~datetime.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)})
client.db.collection.insert_one({"x": DatetimeMS(2**62)})
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 类的 API 文档。

另一个不涉及设置 datetime_conversion 的选项是过滤掉超出 ~datetime.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})

以下类似的错误消息表示 OpenSSL 无法验证服务器的证书

[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

这通常是因为 OpenSSL 无法访问系统的根证书,或者证书已过期。

如果您使用 Linux,请确保您已从您的 Linux 提供商安装了最新的根证书更新。

如果您使用 macOS,并且您从 python.org 下载了 Python 3.7 或更高版本,请运行以下命令安装根证书

open "/Applications/Python <YOUR PYTHON VERSION>/Install Certificates.command"

提示

有关此问题的更多信息,请参阅 Python 问题 29065。

如果您使用便携式pypy,可能需要设置环境变量以告知OpenSSL根证书的位置。以下代码示例展示了如何从PyPi安装certifi模块并导出SSL_CERT_FILE环境变量

$ pypy -m pip install certifi
$ export SSL_CERT_FILE=$(pypy -c "import certifi; print(certifi.where())")

提示

有关此问题的更多信息,请参阅portable-pypy问题15。

以下类似的错误消息表示Python使用的OpenSSL版本不支持足够的TLS协议来连接到服务器

[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version

行业最佳实践建议,某些法规要求,在某些MongoDB部署中禁用较旧的TLS协议。某些部署可能禁用TLS 1.0,而其他部署可能禁用TLS 1.0和TLS 1.1。

PyMongo使用最新的TLS版本不需要进行任何应用程序更改,但某些操作系统版本可能不提供足够新的OpenSSL版本以支持它们。

如果您使用macOS v10.12(High Sierra)或更早版本,请从python.org、homebrew、macports或类似来源安装Python。

如果您使用Linux或其他非macOS Unix系统,请使用以下命令来检查您的OpenSSL版本

$ openssl version

如果前面的命令显示的版本号小于1.0.1,则不支持TLS 1.1或更高版本。请升级到较新版本,或联系您的操作系统供应商以获取解决方案。

要检查Python解释器的TLS版本,请安装requests模块,并执行以下代码

python -c "import requests; print(requests.get('https://www.howsmyssl.com/a/check', verify=False).json()['tls_version'])"

您应该看到TLS 1.1或更高版本。

以下类似错误信息表示证书吊销检查失败

[('SSL routines', 'tls_process_initial_server_flight', 'invalid status response')]

有关更多详细信息,请参阅本指南中的OCSP部分。

当使用Python v3.10或更高版本与MongoDB版本低于v4.0时,您可能会看到以下类似错误信息

SSL handshake failed: localhost:27017: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:997)
SSL handshake failed: localhost:27017: EOF occurred in violation of protocol (_ssl.c:997)

MongoDB服务器日志也可能显示以下错误

2021-06-30T21:22:44.917+0100 E NETWORK [conn16] SSL: error:1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher

Python v3.10中对ssl模块所做的更改可能与MongoDB版本低于v4.0不兼容。为了解决这个问题,尝试以下步骤之一或多个

  • 将Python降级到v3.9或更低版本

  • 将 MongoDB 服务器升级到 v4.2 或更高版本

  • 使用具有 OCSP 选项安装 PyMongo,该选项依赖于 PyOpenSSL

当使用 OpenSSL v3 或更高版本时,您可能会看到类似以下的消息错误

[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled

这些错误类型发生是因为过时或有错误的 SSL 代理错误地强制执行了旧版 TLS 重协商。

要解决这个问题,请执行以下步骤

1

运行以下命令以确保您已安装 OpenSSL v3.0.4 或更高版本

openssl version
2

创建一个包含 UnsafeLegacyServerConnect 选项的配置文件。以下示例展示了如何设置 UnsafeLegacyServerConnect 选项

openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = UnsafeLegacyServerConnect
3

在设置 OPENSSL_CONF 环境变量以使用您刚刚创建的 OpenSSL 配置文件的同时运行 Python

OPENSSL_CONF=/path/to/the/config/file/above.cnf python ...

重要

由于设置 UnsafeLegacyServerConnect 选项存在 安全影响,因此作为最后手段,使用此 workaround 解决 不安全旧版协商禁用 错误。

此错误表示客户端在给定超时时间内找不到可运行操作的可用服务器

pymongo.errors.ServerSelectionTimeoutError: No servers found yet, Timeout: -0.00202266700216569s, Topology Description: <TopologyDescription id: 63698e87cebfd22ab1bd2ae0, topology_type: Unknown, servers: [<ServerDescription ('localhost', 27017) server_type: Unknown, rtt: None>]>

此错误表示客户端在给定超时时间内无法建立连接,或者操作已发送但服务器未及时响应

pymongo.errors.NetworkTimeout: localhost:27017: timed out

此错误可能表示服务器因为超时而取消了操作。即使 PyMongo 抛出此异常,操作也可能已在服务器上部分完成。

pymongo.errors.ExecutionTimeout: operation exceeded time limit, full error: {'ok': 0.0, 'errmsg': 'operation exceeded time limit', 'code': 50, 'codeName': 'MaxTimeMSExpired'}

这也可能表明客户端取消了操作,因为在指定的超时时间内无法完成

pymongo.errors.ExecutionTimeout: operation would exceed time limit, remaining timeout:0.00196 <= network round trip time:0.00427

此错误表示服务器无法在指定的超时时间内并根据指定的写入关注度完成请求的写入操作

pymongo.errors.WTimeoutError: operation exceeded time limit, full error: {'code': 50, 'codeName': 'MaxTimeMSExpired', 'errmsg': 'operation exceeded time limit', 'errInfo': {'writeConcern': {'w': 1, 'wtimeout': 0}}}

此错误表示服务器无法在指定的超时时间内并根据指定的写入关注度完成 insert_many()bulk_write() 方法

pymongo.errors.BulkWriteError: batch op errors occurred, full error: {'writeErrors': [], 'writeConcernErrors': [{'code': 50, 'codeName': 'MaxTimeMSExpired', 'errmsg': 'operation exceeded time limit', 'errInfo': {'writeConcern': {'w': 1, 'wtimeout': 0}}}], 'nInserted': 2, 'nUpserted': 0, 'nMatched': 0, 'nModified': 0, 'nRemoved': 0, 'upserted': []}

一个 MongoClient 实例会启动多个线程来运行后台任务,例如监控连接的服务器。这些线程共享由 threading.Lock 类实例保护的资源,而这些类实例本身不安全地分叉。PyMongo 与任何使用 threading.Lock 类或任何互斥锁的并发代码面临相同的限制。

这些限制之一是调用 fork() 方法后锁变得无效。当 fork() 执行时,驱动程序会将父进程的所有锁及其状态复制到子进程中。如果在父进程中锁定,它们在子进程中也会锁定。由 fork() 创建的子进程只有一个线程,因此父进程中其他线程创建的任何锁在子进程中都不会释放。当子进程下次尝试获取这些锁之一时,就会发生死锁。

从PyMongo版本4.3开始,在调用os.fork()方法后,驱动程序将使用os.register_at_fork()方法来重置子进程中的锁和其他共享状态。尽管这减少了死锁的可能性,但PyMongo依赖于多线程应用程序中不安全的库,包括OpenSSLgetaddrinfo(3).。因此,仍然可能发生死锁。

Linux手册页中关于fork(2)也有以下限制

在多线程程序中进行 fork() 操作后,子进程可以安全地调用仅限异步信号安全的函数(见 signal-safety(7))。直到它调用 execve(2)

由于 PyMongo 依赖于不是异步信号安全的函数,它在子进程中运行时可能会导致死锁或崩溃。

提示

有关子进程中的死锁示例,请参阅 Jira 中的 PYTHON-3406

关于Python在多线程环境中使用fork()产生的锁问题的更多信息,请参阅Python问题跟踪器中的问题6721

返回

常见问题解答