Async SQLAlchemy中AsyncSession两种使用方式差异及相关报错如何解决
两种AsyncSession写法差异及报错解决方案
核心前提
你代码中赋值的AsyncSession是async_scoped_session生成的会话代理对象,而非SQLAlchemy原生的异步会话类。它会根据你指定的current_task scopefunc,为每个异步任务绑定独立的会话实例,同个任务内多次调用AsyncSession()拿到的是同一个实例,不同任务之间会话完全隔离。
一、两种写法的核心区别
- Foo类写法:混合了「类实例持有会话对象」和「全局代理操作会话」两种模式,操作同一个会话时同时用到了实例属性和全局代理,容易出现上下文不一致问题
- Bar类写法:仅通过类实例持有的会话对象操作,没有用到代理的全局上下文隔离特性
二、报错原因分析
1. Bar类触发PendingRollbackError的原因
报错的根本原因是你持有的self.async_session之前执行操作时发生过异常(你给出的报错里明确是主键冲突的UniqueViolationError),会话的事务已经被自动回滚,但你没有主动调用rollback()方法重置会话状态,就继续用同一个会话执行新操作,就会触发这个报错。
Bar类写法容易触发该问题的常见诱因:
- 类实例被跨多个异步任务复用,绑定的会话也被多个任务共享,而AsyncSession本身不是任务安全的,上一个任务执行出错没有处理,下一个任务拿到已失效的会话直接使用
- 没有做异常捕获逻辑,操作出错后没有回滚会话就继续执行后续逻辑
2. Foo类触发remove() await报错的原因
SQLAlchemy 1.4版本中async_scoped_session.remove()是同步方法,没有返回值,你强行加await调用就会出现NoneType不能被await的报错。
三、对应解决方法
针对Bar类的PendingRollbackError
- 不要跨异步任务长期持有AsyncSession实例,每个异步任务的独立逻辑块单独从
async_scoped_session获取会话,不要存在类实例中全局复用 - 所有数据库操作增加异常捕获逻辑,发生异常时主动回滚会话:
async def some_function(self, some_object): async with AsyncSession() as session: try: session.add(some_object) await session.commit() except Exception as e: await session.rollback() raise e
- 推荐直接用异步上下文管理器获取会话,执行完逻辑后会自动关闭会话,不需要手动处理
针对Foo类的remove()报错
直接去掉await关键字,将await AsyncSession.remove()改为AsyncSession.remove()即可。同时不推荐混合使用实例持有会话和全局代理操作的写法,全程要么用全局代理操作,要么用上下文管理器拿到的实例操作,避免出现会话状态不一致。
四、推荐实践
- 不要将AsyncSession作为类属性长期持有,每个异步任务的独立逻辑块单独获取会话
- 所有数据库操作逻辑必须增加异常捕获,异常场景主动回滚事务
- 不要混用代理对象和实例对象操作同一个会话,避免出现不可预期的状态问题
内容的提问来源于stack exchange,提问作者Nyxynyx
相关产品推荐
相关产品推荐

