Docs 菜单
Docs 主页
/ / /
pymongo

故障排除

在此页面上

  • 连接
  • 读取和写入操作
  • Cursors
  • 投影
  • 索引
  • Data Formats
  • TLS
  • 客户端操作超时
  • 分叉进程

在此页面上,您可以找到将 PyMongo 与 MongoDB 结合使用时遇到的常见问题的解决方案。

如果您尝试连接到 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错误,直到副本集选出新的主节点成员。

如果您尝试通过 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 节点。

如果您在读取偏好中指定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 应用程序中,对 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__)
@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 Server 文档中的“嵌入式/嵌套文档查询”指南。

如果您向Cursor构造函数提供无效参数,PyMongo v 3.8或更早版本会引发TypeErrorAttributeErrorAttributeError无关紧要,但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或更高版本,这将消除不相关错误。

MongoDB 中的游标如果长时间打开且未对其执行任何操作,则可能会在服务器上超时。 当您尝试遍历游标时,这可能会导致CursorNotFound异常。

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

如果您执行的写入操作存储了违反唯一索引的重复值,则驾驶员会引发DuplicateKeyException ,并且MongoDB会抛出类似于以下内容的错误:

E11000 duplicate key error index

出现此错误的原因是,当 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})

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})

类似以下的错误消息表示 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 。

类似以下的错误消息表示 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部分。

将 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 重新协商。

要解决此问题,请执行以下步骤:

1

运行以下命令,确保您安装了 OpenSSL vv 3.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

运行Python ,同时将 OPENSSL_CONF 环境变量设置为使用刚刚创建的 OpenSSL 配置文件:

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

重要

由于设置UnsafeLegacyServerConnect 选项存在安全隐患,因此请将此解决方法作为解决unsafe legacy renegotiation disabled 错误的最后手段。

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

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 类实例保护的状态,而这些实例本身 不是分叉安全的 。与使用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 问题跟踪器中。

后退

常见问题解答