必须用async驱动+async依赖项+连接池,三者缺一不可;否则看似async实则线程阻塞,吞吐量不升反降。asyncpg等驱动需显式创建连接池(如create_pool),不能每次请求调用connect();依赖项中yield前必须await获取连接,否则返回协程对象导致500或空响应;压测QPS未提升往往因混用requests、time.sleep等同步操作阻塞事件循环。

直接结论:必须用 async 驱动 + async 依赖项 + 连接池,三者缺一不可;否则看似 async,实则线程阻塞,吞吐量不升反降。
asyncpg / aiomysql 等驱动为什么不能直接 import 后就用?
常见错误是写成 import asyncpg 然后在 async def 里调用 asyncpg.connect() —— 这会导致每次请求都新建连接,压测时很快耗尽数据库连接数或触发超时。
- 异步驱动本身不等于自动连接池,
asyncpg.connect()是单次连接,无复用 - 必须显式使用
asyncpg.create_pool()或 ORM 的异步连接池(如 SQLAlchemy 1.4+ 的create_async_engine(pool_pre_ping=True)) - 连接池需在应用启动时初始化(
@app.on_event("startup")),而非每次请求中创建
get_db 依赖项里 yield db 之前必须 await 吗?
必须。很多代码漏掉 await 导致依赖项返回的是协程对象而非实际连接,FastAPI 会静默失败或抛出 TypeError: object ... is not asynchronous。
- 正确写法:
db = await database_pool.acquire()(用asyncpg.Pool)或db = await AsyncSessionLocal()(SQLAlchemy) - 错误写法:
db = database_pool.acquire()—— 返回coroutine对象,后续fetch_one会报RuntimeWarning: coroutine 'Pool.acquire' was never awaited - yield 前若未 await,FastAPI 无法将连接注入路由函数参数,最终表现为 500 或空响应
为什么用了 async 驱动,压测 QPS 却和同步版本差不多?
大概率是混用了同步库,或阻塞了事件循环。FastAPI 的并发收益只对真正异步的 I/O 路径生效。
立即学习“Python免费学习笔记(深入)”;
- 检查是否在
async def中调用了time.sleep()、requests.get()、json.load()等同步操作 —— 它们会冻结整个事件循环 - 外部 HTTP 请求必须换为
httpx.AsyncClient,不能用requests - 文件读写要用
asyncio.to_thread(open, ...)或aiofiles,不能用内置open() - ORM 查询必须用
.execute()+.fetch_all()(SQLModel/Tortoise)或session.execute(...).scalars().all()(SQLAlchemy async),不能用.all()同步方法
连接池 max_size 设多少才合理?
没有固定值,但可按「并发请求数 × 平均每请求 DB 操作数」粗估,再加 20% 缓冲;超过 20–30 通常收益递减,反而增加管理开销。
- 例如:预估峰值 1000 QPS,平均每个请求查 2 张表 → 初始设
min_size=10, max_size=30 - PostgreSQL 推荐
max_connections(数据库侧) ≥ 应用连接池max_size× 实例数,否则新连接被拒 - 务必开启
pool_pre_ping=True(SQLAlchemy)或min_size > 0(asyncpg),避免拿到失效连接 - 闲置连接超时(
pool_recycle或max_inactive_connection_lifetime)建议设为 300–600 秒,防连接僵死
最容易被忽略的一点:所有 async 数据库操作必须全程在同一个事件循环中完成——从依赖项获取连接、到执行查询、再到返回结果,中间不能穿插任何同步阻塞调用。一旦断链,async 就退化为“伪异步”,性能反不如精心调优的同步方案。



















