Motor是唯一适配Tornado异步模型的MongoDB驱动,需用async/await初始化连接、全局复用client、正确await操作、合理配置连接池参数,并注意AsyncIOMotorCursor需显式转为list或异步遍历。

Motor 是唯一能真正匹配 Tornado 异步模型的 MongoDB 驱动,用 pymongo 直接在 async def 里调用会阻塞事件循环——这不是“慢”,而是直接废掉 Tornado 的异步能力。
MotorClient 初始化必须用 async/await 等待连接就绪
很多人以为 AsyncIOMotorClient 实例化即完成连接,其实它只做配置,首次实际操作(如 db.list_collection_names())才触发真实连接。若跳过等待,后续查询可能因连接未建立而超时或静默失败。
- 正确做法是在应用启动时显式 await 一次探测操作,比如
await client.admin.command("ping") - 不要把
client = AsyncIOMotorClient(...)放在 RequestHandler 的get方法里——每次请求都新建 client 会快速耗尽连接池 - 推荐在全局或 Application 初始化阶段创建单例 client,并复用到所有 handler 中
Database 和 Collection 不是线程安全的,但可跨协程共享
db = client.mydb 和 collection = db.mycoll 这两步都是轻量级对象构建,不涉及网络 I/O,可以安全地在多个协程中复用。但注意:它们本身不保存状态,所有操作仍需 await。
- 别在每次请求里重复写
db = client["mydb"],直接复用已初始化的db变量即可 - Collection 对象没有内置缓存,
db.foo和db["foo"]效果一致,无需提前“预热” - 如果需要动态库名(如多租户),用
client[tenant_db_name]是安全的,但确保tenant_db_name已做过白名单校验
查询必须用 await,且避免在非 async 上下文中调用
Tornado 的 RequestHandler 默认不是 async 的,直接在 get() 里写 await collection.find_one(...) 会报 RuntimeWarning: coroutine 'find_one' was never awaited,最终返回空或引发 TypeError。
- Handler 必须继承
tornado.web.RequestHandler并将方法声明为async def get(self) - 所有 Motor 操作都返回 coroutine 对象,必须用
await,不能用.result()或asyncio.get_event_loop().run_until_complete() - 错误示例:
doc = collection.find_one({"_id": ObjectId(x)})—— 这行代码什么都没执行,只是生成了一个未被 await 的协程对象
连接池参数不设默认值容易拖垮服务
Motor 默认 maxPoolSize=100,看似宽松,但在高并发场景下,每个请求占一个连接 + 多个中间件协程同时 hold 连接,很容易打满池子,导致后续请求卡在 waitQueueTimeoutMS 上。
- 生产环境建议显式设置
maxPoolSize=50、minPoolSize=5,并配合maxIdleTimeMS=60000防止连接被防火墙中断 -
connectTimeoutMS和socketTimeoutMS必须设(如各 5000),否则网络抖动时请求会长时间挂起 - 不要依赖 URI 参数覆盖全部配置;混合使用 URI 和关键字参数时,关键字参数优先级更高,但部分参数(如
minPoolSize)URI 中不支持,必须用关键字传入
最常被忽略的一点:Motor 的 find() 返回的是 AsyncIOMotorCursor,不是 list。直接 await cursor 会报错,必须用 await cursor.to_list(length=100) 或遍历 async for doc in cursor —— 这个细节一旦漏掉,日志里只显示空响应,很难定位。


















