Motor不是PyMongo的异步封装,而是基于asyncio重写的异步驱动;PyMongo所有I/O操作同步阻塞,不可await,否则报TypeError;Motor需用await调用、async for遍历游标,并显式管理客户端生命周期。

Motor 是 PyMongo 的异步封装,不能直接 await PyMongo 的方法,必须用 Motor 提供的对应类和方法。
为什么不能直接在 asyncio 里用 PyMongo
PyMongo 所有 I/O 操作(如 find_one、insert_one)都是同步阻塞的,放进 async def 函数里 await 会报 TypeError: object dict can't be used in 'await' expression。Motor 重写了整个客户端栈,底层用 asyncio + libev 或 asyncio.get_event_loop().run_in_executor(取决于版本)做非阻塞调度。
常见错误现象:
- 误写
await collection.find_one(...)(PyMongo 返回 dict,不可 await) - 混用
motor.motor_asyncio.AsyncIOMotorClient和pymongo.MongoClient实例 - 忘记在连接字符串中显式加
appname或maxPoolSize,导致高并发下连接耗尽
初始化 AsyncIOMotorClient 的关键参数
Motor 客户端不是线程安全的,也不该被多个协程共享未加锁的实例——但可以安全地复用同一个 AsyncIOMotorClient 实例(它内部管理连接池)。重点参数:
-
host和port可以合并为host="mongodb://localhost:27017",推荐用 URI 形式 -
maxPoolSize=100:默认是 100,但若 QPS 超过 500,建议调到 200+;注意 MongoDB 服务端也有maxConns限制 -
minPoolSize=5:避免冷启动时每次新建连接,但设太高会提前占满服务端连接数 -
serverSelectionTimeoutMS=3000:超时设太短会导致偶发ServerSelectionTimeoutError,尤其在副本集切换期间 -
connect=False:设为True会在实例化时立即尝试连接(同步阻塞),应保持默认False,改用await client.admin.command("ping")显式探测
常用操作的写法与陷阱
所有集合级操作返回的是 coroutine 对象,必须 await;游标(AsyncIOMotorCursor)也需 await 才能取值。典型写法:
client = AsyncIOMotorClient("mongodb://localhost:27017")
db = client["mydb"]
collection = db["users"]
<h1>✅ 正确:await 每个异步操作</h1><p>user = await collection.find_one({"_id": ObjectId("...")})
await collection.insert_one({"name": "Alice", "ts": datetime.now()})</p><h1>❌ 错误:没 await,user 是 coroutine 对象,后续 .get() 会报 AttributeError</h1><p>user = collection.find_one({"_id": ...}) # 忘了 await</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill11526" title="Python Hardener"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20dbae7c507.jpg" alt="Python Hardener" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill11526" title="Python Hardener" class="overflowclass">Python Hardener</a>
<p class="overflowclass">通过完整的安全审查、代码整洁重构、健壮的错误处理、正确的日志记录以及完整的文档字符串覆盖,强化 Python 脚本。</p>
</div>
<a rel="nofollow" href="/xiazai/skill11526" title="Python Hardener" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h1>✅ 游标要 await list() 或用 async for</h1><p>async for doc in collection.find({"status": "active"}):
print(doc["name"])</p><h1>⚠️ 注意:collection.find() 本身不触发网络请求,只有遍历或 await list() 才真正执行</h1><p></p>
性能影响点:
- 频繁
await collection.find().to_list(length=1000)会一次性加载全部文档进内存,大数据量时易 OOM;优先用async for -
update_many默认不返回匹配数,要加upsert=False, bypass_document_validation=False等显式控制行为 - 事务必须在 session 内执行:
async with await client.start_session() as s: await s.start_transaction(),漏掉start_transaction会导致写操作不被隔离
连接泄漏与生命周期管理
Motor 客户端没有自动关闭机制,client.close() 是异步方法,必须显式 await;否则进程退出前未 close 会导致连接句柄泄漏,Docker 环境下表现为 Connection refused 或 Too many open files。
推荐模式:
- 全局单例 client(模块级变量),应用启动时创建,退出前
await client.close() - Web 框架(如 FastAPI)中用 lifespan event:
@app.on_event("shutdown")中 await close - 测试时每次用
pytest-asynciofixture 创建新 client,并确保 teardown 中 await close
容易被忽略的是:即使只读操作,也要等 await client.close() 完成后再让主程序 exit,否则部分连接可能卡在 FIN_WAIT2 状态,持续占用端口。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










