必须用AsyncIOMotorClient而非PyMongo.MongoClient,因后者同步阻塞会卡死事件循环;客户端须全局单例并在startup/shutdown中管理生命周期;collection操作须await,游标需async for或to_list消费,禁止固化db或collection引用。

必须用 AsyncIOMotorClient,不能用 PyMongo.MongoClient
PyMongo 的 MongoClient 是同步阻塞的,哪怕包装成 async def 并加 await,它仍会卡住整个事件循环。FastAPI 的并发能力直接归零,QPS 可能掉到个位数。
Motor 的 AsyncIOMotorClient 才是真正基于 asyncio 实现的驱动,所有 I/O 操作(如 find_one、insert_one、admin.command)返回协程对象,必须显式 await 才执行。
-
await client.admin.command("ping")✅ 正确:这是可 await 的协程调用 -
await client❌ 错误:client 本身不是协程,无法 await -
client.find_one({})❌ 错误:不 await 就只是构建查询对象,没发请求
客户端必须全局单例 + 生命周期绑定 startup/shutdown
每次请求都新建 AsyncIOMotorClient 实例,会导致连接池失控、DNS 频繁解析、内存泄漏——上线后几小时就可能 OOM 或连接耗尽。
正确做法是:在应用启动时创建一次 client,在关机时调用 client.close()(注意不是 await client.close(),它不返回协程)。
- ✅ 在
@app.on_event("startup")中初始化client = AsyncIOMotorClient(...) - ✅ 在
@app.on_event("shutdown")中调用client.close() - ❌ 不要在
Depends函数里写AsyncIOMotorClient(...) - ❌ 不要在模块顶层直接
client = AsyncIOMotorClient(...)(此时无法 await 初始化检查)
collection.find() 返回的是游标,不能直接 for 循环或索引
collection.find({}) 返回的是 AsyncIOMotorCursor,它既不是 list,也不支持同步迭代。常见报错:TypeError: 'AsyncIOMotorCursor' object is not iterable。
必须用异步方式消费:
- ✅
async for doc in collection.find({...}):适合流式处理、SSE、大数据分页 - ✅
await cursor.to_list(length=100):适合小批量全量加载;length=None要慎用,可能 OOM - ✅
await collection.find_one({...}):等价于await (await collection.find({...})).next() - ❌
for doc in collection.find({...}):同步 for,直接报错 - ❌
collection.find({...})[0]:不支持索引访问
数据库和 collection 引用不能固化为全局变量
像 db = client["mydb"] 或 users_col = db["users"] 这种赋值一旦写成模块级变量,多租户、灰度发布、测试/生产隔离等场景下极易串库或越权。
MongoDB 的权限模型依赖于数据库级上下文,固化引用等于绕过动态鉴权逻辑。
- ✅ 用
Depends动态返回AsyncIOMotorDatabase,让业务层按需选 collection - ✅ 封装带参数的依赖:如
def get_collection(name: str) -> AsyncIOMotorCollection - ✅ 聚合管道中涉及
$lookup时,务必在被关联集合上建索引,Motor 不会预警,但延迟可能从毫秒跳到秒级 - ⚠️ 写操作后记得检查
result.acknowledged,尤其在w:0或网络抖动时可能静默失败
?retryWrites=true 是硬性要求,否则部分写操作在副本集切换时可能丢失;而 maxPoolSize 和 minPoolSize 参数看似可配,实际多数情况保持默认更稳——调得太激进反而容易触发连接风暴。


















