You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQLAlchemy会话意外失效引发PendingRollbackError及FastAPI会话使用咨询

问题背景

我在FastAPI项目中使用SQLAlchemy 2.0.20作为ORM库。每次向API服务器发起请求时,路由需要一个sqlalchemy.orm.session.Session对象,我要么通过DbSession = sessionmaker(engine)重新创建,要么从缓存中获取,代码如下:

engine = create_engine(
    connection_string(),
    poolclass=QueuePool,
    pool_size=15,
    pool_recycle=3600,
)
DbSession = sessionmaker(engine)

@cache
def get_db_session() -> Session:
    session = DbSession()
    return session  # 该会话被用于FastAPI路由

后来API突然无法正常运行,原因是缓存的会话对象被标记为无效(具体原因未知),使用会话时抛出错误:PendingRollbackError: Can't reconnect until invalid transaction is rolled back。

我的仓库类同样被跨请求缓存,代码如下:

class SqlRepository:  # 该仓库对象也会跨API请求被缓存(和会话一样)
   def __init__(session: Session):
       self.session = session

   def get_all_todos(self):
       self.session.query(TodoORM).all()  # 此处抛出PendingRollbackError
       ...

我的问题:

  1. 是否应为每个API请求重新创建会话(即不缓存会话)?
  2. 是否应在get_all_todos中处理PendingRollbackError?

另外,我缓存会话的初衷是想复用连接池中的连接,使用一个只需实例化一次的长生命周期会话。请问传入会话实例化仓库的模式是否正确?还是仓库应持有sessionmaker,每次执行数据库事务时重新创建会话?


解答

1. 是否应为每个API请求重新创建会话?

必须为每个请求创建新会话,绝对不能缓存会话。

SQLAlchemy的Session设计就是短生命周期、请求级别的:

  • Session会维护自身的事务状态(比如未提交的修改、事务上下文),跨请求复用会导致事务状态混乱,比如前一个请求的未回滚事务污染后续请求。
  • 当Session对应的连接被池回收、或者数据库端断开连接时,Session会进入无效状态,此时复用就会抛出你遇到的PendingRollbackError。
  • 连接池本身已经帮你处理了连接复用,无需通过缓存Session来实现连接复用——Session每次需要数据库操作时,会自动从连接池获取可用连接,操作完成后(Session关闭时)将连接归还池。

FastAPI的最佳实践就是用依赖项为每个请求生成新Session:

def get_db_session() -> Generator[Session, None, None]:
    session = DbSession()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()

# 路由中使用
@app.get("/todos")
def get_todos(db: Session = Depends(get_db_session)):
    repo = SqlRepository(db)
    return repo.get_all_todos()

2. 是否应在get_all_todos中处理PendingRollbackError?

不需要,也不应该。

这个错误本质是Session生命周期管理不当导致的,修复Session的创建/销毁逻辑就能从根源解决问题,而不是在业务代码里做异常兜底。如果坚持缓存Session,即使处理了这个错误,后续还会出现其他事务状态相关的问题(比如脏读、重复提交),无法彻底解决。

关于仓库模式的选择

传入请求级别的Session实例化仓库是正确的模式,但要注意:

  • 仓库对象也不能跨请求缓存,应该为每个请求创建新的仓库实例,传入当前请求的Session。
  • 不要让仓库持有sessionmaker并自行创建Session——这样会把事务管理的逻辑分散到仓库中,不利于统一控制(比如全局的事务提交/回滚、Session关闭)。

正确的做法是:

  1. 用FastAPI依赖项提供请求级别的Session。
  2. 路由中获取Session,实例化仓库。
  3. 仓库仅负责封装数据库操作,依赖传入的Session完成事务。

这样既保证了Session的正确生命周期,又能通过连接池复用数据库连接,同时保持代码的清晰分层。


内容的提问来源于stack exchange,提问作者tenticon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 11:47:08