IntegrityError表示业务规则被破坏(如重复主键),应捕获并检查orig属性中的数据库错误码;NoResultFound表示查询无结果,需单独处理以避免逻辑混淆,二者语义不同、处理策略各异。

SQLAlchemy操作报错时,IntegrityError和NoResultFound该怎么区分处理
直接捕获具体异常比用通用Exception更安全。比如插入重复主键或唯一约束冲突会抛sqlalchemy.exc.IntegrityError,而session.execute(...).scalar_one()查不到数据则抛sqlalchemy.orm.exc.NoResultFound。两者语义完全不同:前者是业务规则被破坏(比如用户重复注册),后者是查询条件不匹配(比如查一个不存在的ID),混在一起处理容易掩盖逻辑问题。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 对写操作(
add/commit)优先捕获IntegrityError,检查orig属性里的原生数据库错误码,如PostgreSQL的23505(唯一约束违规) - 对读操作(
scalar_one/one)单独捕获NoResultFound,避免用first()再判空——它不抛异常但可能引入N+1或空对象误用 - 别在事务外捕获
IntegrityError后静默吞掉,至少记录str(e)和触发SQL,否则线上无法定位哪条数据/哪个字段违规
事务回滚后,session还能继续用吗
能,但必须重置状态。SQLAlchemy的Session在commit()失败或显式调用rollback()后进入“失效”状态,此时再访问已加载对象的延迟属性(如关系字段)会抛sqlalchemy.orm.exc.DetachedInstanceError,因为会话已与数据库连接断开同步。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 发生异常并
rollback()后,立刻调用session.expunge_all()清除所有已跟踪对象,或直接新建Session实例 - 不要在同一个
session里混合成功提交和失败回滚的操作——比如先add()再commit()失败后,又试图add()新对象,这会导致状态混乱 - 用
session.begin_nested()做保存点(savepoint)比全局rollback()更轻量,适合在大事务中隔离局部失败
为什么flush()报错比commit()更难调试
flush()把Python对象变更同步到数据库缓冲区,不涉及事务提交,但它会提前暴露SQL生成、类型转换、约束检查等问题。问题在于:它不一定会立刻抛错(比如外键引用未flush的临时对象),且错误堆栈指向ORM内部方法,而非你的业务代码行。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 在关键路径上手动加
session.flush(),而不是依赖commit()自动触发——这样能更快暴露数据一致性问题 - 捕获
FlushError时,检查session.new和session.dirty里哪些对象还没刷出,配合session.identity_map确认对象是否已持久化 - 避免在
__init__或@hybrid_property里触发flush(),这类隐式调用会让错误源头难以追踪
异步SQLAlchemy(async_session)的异常处理有啥不同
异步驱动下,几乎所有数据库操作都变成await调用,异常类型不变,但传播方式变了:IntegrityError可能从execute()、scalars()甚至refresh()中抛出,且不能用传统try/except包住整个async with块——因为AsyncSession的生命周期由上下文管理器控制,异常未被捕获会导致连接泄漏。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 每个
await调用单独包裹try/except,尤其是session.commit()和session.rollback()——它们本身也可能抛AsyncIOError - 别在
async with AsyncSession() as session:块内做耗时非DB操作,否则异常发生时会拖长连接占用时间 - 用
session.sync_session临时切回同步模式调试时,记得异常后手动session.sync_session.rollback(),否则异步会话状态会错乱
最麻烦的其实是嵌套事务和多线程共用Session,那种场景下异常可能根本不会按预期抛出——得靠session.bind.echo = True先看清SQL执行顺序,再决定在哪一层拦截。

















