FastAPI中SQLAlchemy同步会话性能远超异步的原因探究
问题背景
开发了两个FastAPI应用版本:
- 异步版本:采用SQLAlchemy异步数据库连接
- 同步版本:采用SQLAlchemy同步连接
性能测试结果差异巨大:
- 异步版本:1000次请求耗时约500秒
- 同步版本:1000次请求耗时0.72秒
环境配置:
- FastAPI:0.95.1
- SQLAlchemy:2.0.19
- 数据库:SQLite
- PyNest:0.1.0
核心原因分析
1. SQLite的异步本质局限性
SQLite是单文件、单线程数据库,即使使用aiosqlite异步驱动,底层依然无法实现真正的并行IO操作——SQLite的写操作依赖全局锁,读操作在写锁存在时也会阻塞。异步版本的"异步"仅将阻塞操作转移到事件循环线程,没有真正提升并行处理能力,反而因异步框架的调度开销,比同步版本更慢。
2. 同步版本的会话复用与逻辑差异
同步版本的TransactionService使用@lru_cache()装饰器,意味着整个应用生命周期内仅初始化一次实例,对应的数据库会话会被复用,省去了频繁创建、销毁会话的开销;而异步版本的get_db()每次请求都会创建新的异步会话,请求结束后关闭,这部分额外开销被1000次请求放大。
另外,同步版本的get_transactions()仅返回first()数据,而异步版本返回all()——若数据库中数据量较大,这会进一步加剧性能差异。
3. 异步框架的冗余调度开销
异步事件循环需要处理协程切换、调度,对于SQLite这种本身无法并行的场景,这些额外调度完全是冗余的,直接拖慢了整体性能。
异步版本优化建议
1. 更换支持真正异步的数据库
若要发挥异步框架优势,建议将SQLite替换为PostgreSQL、MySQL等支持真正异步IO的数据库,配合asyncpg、aiomysql等异步驱动,才能让异步连接的并行能力得到发挥。
2. 优化异步会话管理
避免每次请求创建新会话,通过连接池复用会话:
class Config: def __init__(self): # 配置连接池参数,控制会话复用 self.engine = create_async_engine( "sqlite+aiosqlite:///finance.db", connect_args={"check_same_thread": False}, pool_size=10, max_overflow=20 ) self.SessionLocal = async_sessionmaker(self.engine) self.Base = Base async def get_db(self): async with self.SessionLocal() as session: yield session
使用async with管理会话,确保会话正确复用,减少创建/销毁的开销。
3. 对齐测试逻辑与数据返回
- 将异步版本接口的
query.scalars().all()改为返回first()(与同步版本逻辑对齐),或改为分页返回,减少数据序列化和传输的开销。 - 调整测试脚本的并发数,1000个同时请求会导致SQLite锁竞争加剧,可降低并发数或采用逐步加压的测试方式。
4. 评估异步必要性
若坚持使用SQLite,同步版本的性能已足够,无需强行使用异步。异步框架更适合IO密集型、高并发且数据库支持异步的场景。
内容的提问来源于stack exchange,提问作者ItayDar

