在 SQLAlchemy 的开发中,expire_on_commit 是一个非常关键的配置参数,尤其是在使用 异步 (Asyncio) 驱动或构建 Web API 时。
下面是一篇关于 expire_on_commit 的深度解析文章,帮助你彻底理解它的原理、作用以及为什么在你的代码中将其设置为 False。
深度解析 SQLAlchemy 的 expire_on_commit 参数
在配置 SQLAlchemy 的 sessionmaker 时,我们经常会看到这样一行代码:
session_factory = async_sessionmaker(engine, expire_on_commit=False)
很多开发者习惯性地跟着教程将其设置为 False,但并不清楚为什么要这样做。本文将从底层原理出发,带你了解这个参数的“前世今生”。
一、 什么是 expire_on_commit?
简单来说,expire_on_commit 决定了 在 Session 执行 commit() 提交事务后,当前 Session 里的数据库对象(Model 实例)是否还要保持“有效”状态。
- 默认值:
True - 作用对象: 处于 Session 追踪下的所有 Persistent(持久化)状态的对象。
二、 工作原理:过期与延迟加载
1. 当 expire_on_commit=True(默认值)时:
当你调用 await session.commit(),SQLAlchemy 会将当前 Session 中所有已经加载的对象属性全部 “过期化”(Expire)。
- 发生了什么: 对象在内存中依然存在,但它内部持有的数据(比如用户的姓名、邮箱等)被清空了。
- 后果: 如果你在 commit 之后尝试访问
user.name,SQLAlchemy 会发现这个属性已过期,于是它会自动触发一次新的 SQL 查询(Lazy Load),去数据库里重新拉取最新的值。
2. 当 expire_on_commit=False 时:
执行 commit() 后,对象持有的数据依然保留在内存中,不会被标记为过期。
- 后果: 在 commit 之后访问
user.name,直接从内存中读取,不会触发新的数据库查询。
三、 为什么异步编程(Async)通常设为 False?
这是最核心的问题。在异步 SQLAlchemy 中,默认的 True 经常会导致程序崩溃。
1. 避免隐式的异步 IO 异常
在异步环境下,SQLAlchemy 要求所有的数据库操作必须通过 await 显式执行。
如果 expire_on_commit=True:
- 你
await session.commit()。 - 你尝试打印
print(user.name)。 - SQLAlchemy 尝试自动触发一次
SELECT来刷新过期的数据。 - 报错! 因为
user.name的访问是一个同步操作,而它触发的底层数据库查询是异步的。同步代码中无法处理异步 IO,系统会抛出MissingGreenlet或AttributeError相关的错误。
2. 性能考量
在 Web 开发中,我们通常的操作逻辑是:
查询数据 -> 修改数据 -> 提交 -> 将数据序列化并返回给前端。
如果设为 True,在返回给前端(序列化)的过程中,访问每个属性都会触发一次额外的 SELECT,这会导致严重的 N+1 查询问题,让原本高性能的异步程序变慢。
四、 代码对比示例
情况 A:expire_on_commit=True (可能报错或性能低)
async with session_factory() as session:
user = await session.get(User, 1)
user.last_login = datetime.now()
await session.commit() # 数据提交了,但 user 里的数据被清空了
# 此时 user.name 是过期的
print(user.name) # 报错!试图在同步环境触发异步刷新
情况 B:expire_on_commit=False (安全、高效)
async with session_factory() as session:
user = await session.get(User, 1)
user.last_login = datetime.now()
await session.commit() # 提交了,user 里的数据依然留在内存中
print(user.name) # 正常打印,不产生额外的数据库查询
五、 这个参数会有负面影响吗?
既然 False 这么好,为什么官方默认是 True?
原因在于:数据一致性。
如果 expire_on_commit=False,在你的事务提交后,如果 另外一个并发的程序 修改了数据库里的这条记录,你的 user 对象持有的依然是 commit 瞬间旧数据。
True的逻辑: 每次提交后都重新查一遍,确保你看到的是数据库里最“新鲜”的数据。False的逻辑: 相信内存里的数据,不再去麻烦数据库。
在 Web 开发中,通常一个请求的生命周期非常短,我们并不担心在这个极短的时间内数据被其他进程修改,因此使用 False 是利大于弊的。
六、 总结与最佳实践
- 异步开发必设
False:在使用async_sessionmaker时,几乎永远应该设置expire_on_commit=False。 - Web 序列化友好:它能确保你在 commit 之后,依然可以轻松地将模型对象转化为 JSON 字典。
- 手动刷新:如果你确实需要获取数据库里最新的状态,可以使用
await session.refresh(instance)手动触发刷新,而不是依赖自动过期。
一句话总结:
expire_on_commit=False 是为了防止对象在事务提交后“失忆”,它是异步 SQLAlchemy 开发中的“免坑金牌”。