FastAPI+Uvicorn应用中SQLAlchemy QueuePool溢出问题排查
问题分析与解决方案
核心原因
你遇到的连接池耗尽问题,核心是对单线程Uvicorn的工作模式理解有误:
- 若你的API端点是用
def定义的同步函数,Uvicorn会自动启动一个默认40个线程的线程池来并行处理这些请求,每个线程会独立创建数据库会话,直接超过你设置的pool_size=10 + max_overflow=20的总连接数上限。 - 即使是异步端点,若使用同步SQLAlchemy会话,异步事件循环会为阻塞的DB操作额外启动线程,同样会产生多个并发连接,耗尽连接池。
解决方案
方案1:改用异步SQLAlchemy(推荐)
适配FastAPI的异步特性,使用SQLAlchemy异步引擎和会话,从根源避免线程池带来的并发问题:
# 异步数据库配置 from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker engine = create_async_engine( settings.DATABASE_URI, pool_pre_ping=True, pool_size=10, max_overflow=20 ) AsyncSessionLocal = sessionmaker( autocommit=False, autoflush=False, bind=engine, class_=AsyncSession ) # 异步依赖函数 async def get_db(): async with AsyncSessionLocal() as session: yield session
对应的异步端点写法:
from sqlalchemy import select from your_models import Item @app.get("/items/{item_id}") async def read_item(item_id: int, db: AsyncSession = Depends(get_db)): result = await db.execute(select(Item).where(Item.id == item_id)) item = result.scalar_one_or_none() return item
方案2:限制Uvicorn同步线程池大小
若暂时无法重构为异步,调整Uvicorn的并发限制,让线程池大小不超过数据库连接池总容量(30):
命令行启动时配置
uvicorn main:app --workers 1 --limit-concurrency 30
代码内配置
if __name__ == "__main__": import uvicorn uvicorn.run( "main:app", host="0.0.0.0", port=8000, workers=1, limit_concurrency=30 )
同时要优化同步端点的数据库操作,减少连接持有时间(比如避免慢查询)。
方案3:调整数据库连接池参数
若业务确实需要更高并发,可适当调大连接池参数,但要注意不超过MySQL的max_connections配置(默认151):
engine = create_engine( settings.DATABASE_URI, pool_pre_ping=True, pool_size=20, # 增大基础连接数 max_overflow=30, # 增大溢出连接数 pool_recycle=3600 # 自动回收闲置1小时的连接,避免长期占用 )
额外排查点
- 验证连接是否正确释放:在
get_db的finally块添加日志,确认每个会话都被关闭:
def get_db(): db = SessionLocal() try: yield db finally: print(f"Closing session {db}") db.close()
- 检查慢查询:长时间运行的SQL会让连接长期占用,导致连接池耗尽,可通过MySQL的慢查询日志定位并优化。
内容的提问来源于stack exchange,提问作者unique_alex020
相关产品推荐
相关产品推荐

