bigywater.tech
Flask · 90

Rollback 的实际行为

rollback 之后 session 里的对象是什么状态?哪些东西回滚不了?commit 失败之后为什么还要手动 rollback?这篇用真实的 PostgreSQL 把每一种情况跑一遍。

transient
已 flush 的新对象在 rollback 之后的状态
4 → 5
回滚的 INSERT 用掉的 id 与下一次成功插入的 id,序列号不回退
PendingRollbackError
commit 失败后没 rollback 就继续使用 session 的异常

对象的状态

实测 · 本机执行输出
  a(改了 bal)                persistent             → persistent (expired)
  new(已 flush 的新对象)        persistent             → transient
  d(已 delete + flush)      deleted                → persistent (expired)
  rollback 之后访问 a.bal = 100 (重新发了 1 条 SELECT;内存里改成 999 的值丢了)
  d 回到数据库里: True

上一篇:Session Flush 与 Commit;下一篇:数据库连接池。

序列号

实测 · 本机执行输出
  回滚掉的 INSERT 用过 id=4;下一次成功插入的 id=5(中间有空洞)

保存点

实测 · 本机执行输出
  outer 在库里: True | inner 在库里: False

commit 失败之后

实测 · 本机执行输出
  第一次 commit → IntegrityError / UniqueViolation
  没 rollback 就继续 commit → PendingRollbackError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a
  rollback() 时的警告: SAWarning: Session's state has been changed on a non-active transaction - this state will be discarded.
  rollback 之后重新来 → 成功,库里有 after_fail: True

Core 连接的 aborted 事务

实测 · 本机执行输出
  违反唯一约束: UniqueViolation
  接着执行 select 1 → InFailedSqlTransaction : current transaction is aborted, commands ignored until end of transaction block
  rollback 之后 select 1 → 1

commit 之后 rollback

实测 · 本机执行输出
  commit 后再 rollback,库里的 committed: 1

Flask 请求结束时

实测 · 本机执行输出
  视图 flush 后抛异常(HTTP 500),库里的 viewlost: 0
  同时 PG 里是否留下 idle in transaction 的连接: 0

分步演示

Rollback 的实际行为
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。Flask-SQLAlchemy 3.1.1、psycopg 3.3.6、自己起的 PostgreSQL(非默认端口)
    • 没有验证:MySQL 等不支持事务内 DDL 回滚的数据库、rollback 与触发器或外部副作用(发邮件、写文件)的关系、两阶段提交、多个 bind 之间的事务一致性
    京ICP备2026067502号-1京公网安备11010502063487号