bigywater.tech
Flask · 93

expire_on_commit

expire_on_commit 默认是 True,它到底在做什么?关掉它能省多少、会丢什么?这篇用真实的 PostgreSQL 和一个会改列的触发器,把两种设置下 commit 之后的对象逐项对照。

1000 → 0
commit 1000 个对象后逐个读属性的 SQL 条数(True 与 False)
DetachedInstanceError
默认设置下请求结束后读 commit 过的对象的属性
0 vs 1
关掉过期后,触发器改过的 ver:对象里的值与库里的值

commit 之后读属性

实测 · 本机执行输出
  expire_on_commit=True  commit 之后读 n.title 与 n.id:新发 1 条 SQL → SELECT note.id, note.title, note.ver, note.updated FROM note WHERE not
  expire_on_commit=False commit 之后读 n.title 与 n.id:新发 0 条 SQL

上一篇:Pool Checkout 与连接释放;下一篇:乐观锁。

请求之外读属性

实测 · 本机执行输出
  expire_on_commit=True  请求结束后读 box[0].title → DetachedInstanceError
  expire_on_commit=False 请求结束后读 box[0].title → 读到 'x'

陈旧数据

实测 · 本机执行输出
  expire_on_commit=True  库里实际: mine|2 | 本会话对象 n.title = 'mine', n.ver = 2
  expire_on_commit=False 库里实际: mine|2 | 本会话对象 n.title = 'mine', n.ver = 0

数据库侧改的列

实测 · 本机执行输出
  expire_on_commit=True  更新并 commit 后对象 n.ver = 1(提交前 0),库里实际 ver = 1
  expire_on_commit=False 更新并 commit 后对象 n.ver = 0(提交前 0),库里实际 ver = 1

代价

实测 · 本机执行输出
  expire_on_commit=True  逐个读 title:  114.0 ms,1000 条 SQL
  expire_on_commit=False 逐个读 title:    0.1 ms,0 条 SQL

分步演示

expire_on_commit
本步的对象

    本文用到的工具与边界

    • 本机 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(session_options={"expire_on_commit": False})、psycopg 3.3.6、自己起的 PostgreSQL(非默认端口,表上有一个 BEFORE UPDATE 触发器)
    • 没有验证:只对部分对象关闭过期(session.expire_on_commit 的局部修改)、eager_defaults 选项、异步 Session 下为什么强烈建议关闭、关闭后配合 refresh 的最佳实践
    京ICP备2026067502号-1京公网安备11010502063487号