bigywater.tech
Flask · 83

Flask 请求与数据库事务边界

一个请求到底是不是一个事务?视图里出错时哪些写入会留下?事务拖得久会怎样?这篇用真实的 PostgreSQL 与多线程的 gunicorn,把事务边界的每个情形跑一遍。

1382 ms
另一个请求更新同一行时被未提交的写事务阻塞的时间
2 个
一个请求里两次 commit 得到的独立事务数(txid 不同)
< 400
after_request 里统一提交的状态码条件(本篇的做法)

默认的边界

实测 · 本机执行输出
  /two-commits 里两次 commit 后各自的 txid_current(): {'t1': 734, 't2': 735} → 两个不同的事务
  /no-commit 只 add 不 commit,库里的行: seed,x-1,x-2

上一篇:SQLAlchemy Session 生命周期;下一篇:Model 与 Table 映射。

出错时哪些留下

实测 · 本机执行输出
  /commit-then-fail → HTTP 500 | 库里的行: seed,y-a

原子性

实测 · 本机执行输出
  /atomic → HTTP 500 | 库里有没有 z-a: False

出错之后继续用 session

实测 · 本机执行输出
   {"after_rollback":true,"first":"IntegrityError","second":"PendingRollbackError"}
  已经执行过查询(事务已自动开始)再调 db.session.begin(): {"begin":"InvalidRequestError: A transaction is already begun on this Session."}
  begin_nested(保存点)里违反唯一约束:只回滚保存点,外层的 sp-keep 仍被提交: True

长事务与锁

实测 · 本机执行输出
  B 更新同一行:等了 1382 ms(A 总共 1684 ms)
  有请求在做只读查询后睡 1.5 秒(没 commit,连接 idle in transaction):B 更新只用了 2 ms

并发用的是 gunicorn 的 gthread worker,B 请求在 A 之后 0.3 秒发出。

统一提交

实测 · 本机执行输出
  /auto-ok   → HTTP 200 | 是否留下 auto-ok: True
  /auto-400  → HTTP 400 | 是否留下 auto-400: False
  /auto-500  → HTTP 500 | 是否留下 auto-500: False

分步演示

Flask 请求与事务边界
本步的对象

    本文用到的工具与边界

    • 本机 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(非默认端口)
    • 没有验证:after_request 里 commit 失败时的响应处理、teardown 里的 rollback 与 Flask-SQLAlchemy 的实现细节的对应关系、隔离级别(READ COMMITTED 以外)、死锁检测、其它数据库(MySQL)的行为
    京ICP备2026067502号-1京公网安备11010502063487号