SQLAlchemy 捕获 IntegrityError 后仍报会话已回滚:把 flush 和 rollback 放对位置
SQLAlchemy 捕获 IntegrityError 后继续查询仍失败时,先确认错误是否发生在 flush,并对同一 Session 显式 rollback,再开始后续事务。
SQLAlchemy ORM 插入记录时,try/except IntegrityError 没有捕获到外键约束错误;把 session.flush() 加进去后,又报 This Session's transaction has been rolled back due to a previous exception during flush。应同时检查异常处理是否覆盖真正的 flush,以及失败后有没有结束这次 Session 事务。
本文针对同步 SQLAlchemy ORM 的事务处理,示例采用当前 Python 和 SQLAlchemy 2.0 写法。直接使用 DBAPI、异步 Session 或框架托管事务,需要按各自的事务接口处理。
add 成功,并不表示数据库已经接受记录
session.add() 通常先将对象纳入 Session,数据库写入发生在 flush。flush 可以由显式调用、提交前的处理或某些查询前的 autoflush 触发。只有 add() 或构造对象放在 try 内,后续真正写库的代码在 try 外,就无法在那里捕获约束异常。
查看第一次失败的堆栈与数据库错误,确认它是外键、唯一键还是其他完整性约束。随后出现的会话回滚错误,往往是在提示应用仍使用这次失败的 Session,不应把第二条错误当成最初失败的原因。
已经打印了数据库 ROLLBACK 也不代表应用可以直接继续。flush 失败后,SQLAlchemy 仍要求应用显式 Session.rollback(),或关闭并结束该 Session 的生命周期,才能把事务边界配对。
把数据库写入与提交纳入同一事务处理
下面是处理结构示例。engine 和映射对象 record 已由项目创建,当前业务单元允许整体回滚:
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
with Session(engine) as session:
try:
session.add(record)
session.flush()
session.commit()
except IntegrityError:
session.rollback()
raise
flush() 让写入发生在异常处理范围内,但不是持久提交;commit() 也必须留在范围内,因为某些约束或其他失败会在提交时才暴露。处理完回滚后,这个示例重新抛出异常,让外层按业务规则返回失败,而不会误报保存成功。
rollback 影响的是当前事务里尚未提交的全部改动,不只出错的那一条记录。不要在混有多项业务的 Session 中,静默回滚后仍告诉用户其他操作已经保存。若框架已拥有外层事务,应把失败交给它的事务处理机制,避免两层代码分别提交或重复接管边界。
更换捕获类为一个更宽泛的异常,不会改变 flush 时机或恢复 Session。代码走 SQLAlchemy 时应捕获它包装的 sqlalchemy.exc.IntegrityError;驱动的原始异常可供内部判断,但不要把含参数值的错误字符串原样返回给访客。
回滚后仍需修复约束失败
如果第一次错误是外键约束,核对引用的父记录是否存在、写入顺序与当前数据库是否正确;唯一约束失败则按该字段的业务唯一性处理。去掉数据库约束或无条件重试同一输入,会绕过或重复触发同一约束错误。
批量导入需要保留其他成功记录时,可以另行设计每批事务或 SAVEPOINT 处理。begin_nested() 的嵌套事务有自己的 flush 时机,不是把所有代码包起来就只回滚一行;应在明确哪些记录可独立失败后安排,不要临时改变整个导入的提交语义。
修改前保留当前事务管理代码和应用版本,在隔离数据中覆盖成功写入、约束失败与同一请求的后续处理。验收应确认失败发生在预期处理范围,事务正确结束,后续不会再因该失败收到会话状态错误,也不会把失败的数据报告为已保存。
若异常被吞掉或提交范围发生变化,恢复原代码并暂停相关写入,先核对实际已提交数据。应用代码回退不会自动撤销此前成功的事务;需要按业务记录对账,不能为了清理异常重建数据库。


