故障排除
在本页,您可以找到使用PyMongo与MongoDB时遇到的常见问题的解决方案。
连接
服务器报告线版本X,PyMongo需要Y
如果您尝试连接到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在副本集选择新的主成员之前,对所有操作都会引发自动重连
错误。
通过隧道使用 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 隧道访问这些地址和端口号。
相反,您可以通过使用带有 SSH 隧道的 directConnection=True
选项直接连接到单个 MongoDB 节点。
读写操作
AutoReconnect
错误
如果您在读取优先级中指定了 tag-sets
,而 MongoDB 无法找到具有指定标记的副本集成员,您将收到此错误。为了避免此错误,在标记集列表末尾包括一个空字典({}
)。这指示 PyMongo 在找不到匹配的标记时从任何匹配读取参考模式的成员中进行读取。
弃用警告: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 配置失败
提供无效的关键字参数名称会导致驱动程序引发此错误。
请确保您指定的关键字参数存在且拼写正确。
在 Web 应用程序中通过 ObjectId 查询文档时无结果
在 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__) 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服务器文档中的查询嵌入式/嵌套文档指南。
游标
'Cursor'对象没有属性'_Cursor__killed'
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或更高版本,这将消除无关的错误。
"CursorNotFound 服务器中 cursor ID 无效"
MongoDB 中的 cursor 在服务器上可能超时,如果它们长时间打开且没有执行任何操作。当您尝试遍历 cursor 时,这可能导致 CursorNotFound
异常。
投影
'在包含投影中不能在字段 <field> 上进行排除'
如果尝试在单个投影中包含和排除字段,驱动程序会返回带有此消息的 OperationFailure
。确保您的投影仅指定要包含的字段或要排除的字段。
索引
重复键异常
如果你执行了一个写入操作,该操作存储了违反唯一索引的重复值,驱动程序将引发一个 DuplicateKeyException
,MongoDB 抛出类似于以下错误的错误
E11000 duplicate key error index
数据格式
ValueError: cannot encode native uuid.UUID with UuidRepresentation.UNSPECIFIED
此错误是由于尝试将本地 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})
使用其他语言驱动器存储的日期解码时发生OverflowError
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.CodecOptions
的 datetime_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})
TLS
CERTIFICATE_VERIFY_FAILED
以下类似的错误消息表示 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。
TLSV1_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 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部分。
SSLV3_ALERT_HANDSHAKE_FAILURE
当使用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 重协商。
要解决这个问题,请执行以下步骤
重要
由于设置 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
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
类实例保护的资源,而这些类实例本身不安全地分叉。PyMongo 与任何使用 threading.Lock
类或任何互斥锁的并发代码面临相同的限制。
这些限制之一是调用 fork()
方法后锁变得无效。当 fork()
执行时,驱动程序会将父进程的所有锁及其状态复制到子进程中。如果在父进程中锁定,它们在子进程中也会锁定。由 fork()
创建的子进程只有一个线程,因此父进程中其他线程创建的任何锁在子进程中都不会释放。当子进程下次尝试获取这些锁之一时,就会发生死锁。
从PyMongo版本4.3开始,在调用os.fork()
方法后,驱动程序将使用os.register_at_fork()
方法来重置子进程中的锁和其他共享状态。尽管这减少了死锁的可能性,但PyMongo依赖于多线程应用程序中不安全的库,包括OpenSSL和getaddrinfo(3).。因此,仍然可能发生死锁。
Linux手册页中关于fork(2)也有以下限制
在多线程程序中进行
fork()
操作后,子进程可以安全地调用仅限异步信号安全的函数(见 signal-safety(7))。直到它调用 execve(2)
由于 PyMongo 依赖于不是异步信号安全的函数,它在子进程中运行时可能会导致死锁或崩溃。
提示
有关子进程中的死锁示例,请参阅 Jira 中的 PYTHON-3406。
关于Python在多线程环境中使用fork()
产生的锁问题的更多信息,请参阅Python问题跟踪器中的问题6721。