故障排除
在此页面上,您可以找到将 PyMongo 与 MongoDB 结合使用时遇到的常见问题的解决方案。
连接
MongoDB Server报告 Wire 版本 X, PyMongo需要 Y
如果您尝试连接到 MongoDB Server v 3.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 部署升级到 v 3.6或更高版本,或降级到 PyMongo v 3 .x,它支持 MongoDB Server v 2.6及更高版本。
自动重新连接
AutoReconnect
异常表示发生了故障转移。 这意味着 PyMongo 已失去与副本集的原始主节点成员的连接,并且其最后一个操作可能已失败。
发生此错误时,PyMongo 会自动尝试为后续操作查找新的主节点成员。 要处理该错误,您的应用程序必须执行以下操作之一:
重试可能失败的操作
继续运行,但需了解操作可能失败
重要
PyMongo 会对所有操作引发AutoReconnect
错误,直到副本集选出新的主节点成员。
使用隧道从 PyMongo 访问 MongoDB 时超时
如果您尝试通过 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 隧道访问这些地址和端口。
相反,您可以使用directConnection=True
选项和 SSH 隧道直接连接到单个 MongoDB 节点。
读取和写入操作
AutoReconnect
错误
如果您在读取偏好中指定tag-sets
,并且 MongoDB 无法找到具有指定标签的副本集成员,您会收到此错误。 要避免此错误,请在标签集列表的末尾包含一个空字典 ( {}
)。 这指示 PyMongo 在找不到匹配标签时,从任何与读取引用模式匹配的成员中读取。
DeprecationWarning:Count 已弃用
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'
MongoClient 失败 ConfigurationError
提供无效的关键字参数名称会导致驱动程序引发此错误。
确保指定的关键字参数存在且拼写正确。
在 Web 应用程序中按 ObjectId 查询文档时没有结果
在 Web 应用程序中,对 URL 中文档的 ObjectId 进行编码很常见,如以下代码示例所示:
"/posts/50b3bda58a02fb9a84d8991e"
Web 框架将 的ObjectId 部分作为URL string传递给请求处理程序。您必须先将该string转换为 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__) 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()
查询在shell中有效,但在PyMongo中无效
在_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 Server 文档中的“嵌入式/嵌套文档查询”指南。
Cursors
“Cursor”对象没有“_Cursor__killed”属性
如果您向Cursor
构造函数提供无效参数,PyMongo v 3.8或更早版本会引发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 v 3.9或更高版本,这将消除不相关错误。
" CursorNotFound 游标 IDID not valid at 服务器"
MongoDB 中的游标如果长时间打开且未对其执行任何操作,则可能会在服务器上超时。 当您尝试遍历游标时,这可能会导致CursorNotFound
异常。
投影
“无法对包含投影中的字段 <field> 进行排除”
如果尝试在单个投影中包含和排除字段,驱动程序会返回OperationFailure
并包含此消息。 确保投影仅指定要包含的字段或要排除的字段。
索引
DuplicateKeyException
如果您执行的写入操作存储了违反唯一索引的重复值,则驾驶员会引发DuplicateKeyException
,并且MongoDB会抛出类似于以下内容的错误:
E11000 duplicate key error index
Data Formats
ValueError: 无法使用 UuidRepresentation.UNSPECIFIED 编码原生 uuid.UUID
出现此错误的原因是,当 UUID 表示形式为UNSPECIFIED
时,尝试将原生UUID
对象编码为Binary
对象,如以下代码示例所示:
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})
解码另一种语言的驱动程序存储的日期时出现 OverflowError
PyMongo 将 BSON datetime
值解码为 Python 的datetime.datetime
类的实例。 datetime.datetime
的实例仅限于datetime.MINYEAR
( 1 ) 和datetime.MAXYEAR
( 9999 ) 之间的年份。 某些 MongoDB 驱动程序可以存储年份值远远超出datetime.datetime
支持的 BSON 日期时间。
有几种方法可以解决这个问题。 从 PyMongo 4.3开始, bson.decode
可以通过四种方式之一解码 BSON datetime
值。 您可以使用 的datetime_conversion
~bson.codec_options.CodecOptions
参数指定转换方法。
默认转换选项为~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})
TLS
CERTIFICATE_VERIFY_FAILED
类似以下的错误消息表示 OpenSSL 无法验证服务器的证书:
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed
发生这种情况的常见原因是 OpenSSL 无法访问系统的根证书,或者证书已过期。
如果使用 Linux,请确保安装了 Linux 供应商提供的最新根证书更新。
如果您使用 macOS,并且运行的是从 python.org 下载的 Python v 3.7或更高版本, 运行以下命令安装根证书:
open "/Applications/Python <YOUR PYTHON VERSION>/Install Certificates.command"
提示
有关此问题的更多信息,请参阅 Python 问题29065 。
如果使用portable-pypy,则可能需要设置一个环境变量来告诉 OpenSSL 在哪里可以找到根证书。 以下代码示例显示了如何安装 certifi 模块 并导出SSL_CERT_FILE
环境变量:
$ pypy -m pip install certifi $ export SSL_CERT_FILE=$(pypy -c "import certifi; print(certifi.where())")
提示
有关此问题的更多信息,请参阅 portable-pypy 问题15 。
TLSV 1 _ALERT_PROTOCOL_VERSION
类似以下的错误消息表示 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 v 10.12 (High Sierra) 或更早版本,请从 python.org 安装 Python, Homebrew、macports 或类似来源。
如果您使用 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部分。
SSLV 3 _ALERT_HANDSHAKE_FAILURE
将 Python v 3.10或更高版本与 v 4.0之前的 MongoDB 版本一起使用时, 您可能会看到类似以下消息的错误:
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 Server 日志还可能显示以下错误:
2021-06-30T21:22:44.917+0100 E NETWORK [conn16] SSL: error:1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher
对 Python v 中的 ssl 模块进行的更改3.10可能会导致与 v 4之前的 MongoDB 版本不兼容。 0 。 要解决此问题,请尝试以下一个或多个步骤:
将 Python 降级到 v 3.9或更早版本
将 MongoDB Server 升级到 v 4.2或更高版本
使用OCSP选项安装 PyMongo,该选项依赖于 PyOpenSSL
已禁用不安全传统重新协商
使用 OpenSSL v 3 或更高版本时,您可能会看到类似以下消息的错误:
[SSL: UNSAFE_LEGACY_RENEGOTIATION_DISABLED] unsafe legacy renegotiation disabled
发生这些类型的错误是由于过时或有缺陷的 SSL 代理错误地实施了传统 TLS 重新协商。
要解决此问题,请执行以下步骤:
客户端操作超时
ServerSelectionTimeoutError
此错误表明客户端在给定超时时间内找不到可用的服务器来运行操作:
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>]>
NetworkTimeout
此错误表示客户端无法在给定超时时间内建立连接,或者操作已发送但服务器未及时响应:
pymongo.errors.NetworkTimeout: localhost:27017: timed out
ExecutionTimeout
此错误可能表示服务器取消了操作,因为超过了给定的超时时间。 即使 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
WTimeoutError
此错误表示服务器无法在给定超时内并在指定的写关注之后完成请求的写入操作:
pymongo.errors.WTimeoutError: operation exceeded time limit, full error: {'code': 50, 'codeName': 'MaxTimeMSExpired', 'errmsg': 'operation exceeded time limit', 'errInfo': {'writeConcern': {'w': 1, 'wtimeout': 0}}}
BulkWriteError
此错误表示服务器无法在给定超时内并在指定的写关注之后完成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
类实例保护的状态,而这些实例本身 不是分叉安全的 。与使用threading.Lock
类或任何互斥锁的任何其他多线程代码一样,PyMongo 也受到相同的限制。
这些限制之一是,在调用fork()
方法后,锁将变得无用。 当fork()
执行时,驱动程序会将父进程的所有锁复制到子进程,其状态与在父进程中的状态相同。 如果它们在父进程中被锁定,那么它们也会在子进程中被锁定。 fork()
创建的子进程只有一个线程,因此父进程中其他线程创建的任何锁都不会在子进程中释放。 下次子进程尝试获取其中一个锁时,就会发生死锁。
从 PyMongo 版本4.3开始,在调用os.fork()
方法后,驱动程序使用os.register_at_fork()
方法重置其锁和子进程中的其他共享状态。 虽然这降低了死锁的可能性,但 PyMongo 依赖于在多线程应用程序中不分叉安全的库,包括 OpenSSL 和 getaddrinfo(3 )。因此,仍可能发生死锁。
fork(2 ) 的 Linux 手册页面 还施加了以下限制:
fork()
在多线程程序中执行 之后,子进程只能安全地调用异步信号安全函数(请参阅 信号安全 (7 ) )直到调用 execve(2 )。
由于 PyMongo 依赖于异步信号不安全的函数,因此在子进程中运行时可能会导致死锁或崩溃。
提示
有关子进程中死锁的示例,请参阅 PYTHON-3406 在 Jira 中。
有关 Python 在多线程上下文中使用fork()
锁所导致问题的详细信息,请参阅 6721问题 在 Python 问题跟踪器中。