创建时间: 2026-09-01最后更新: 2026-09-01

1. 认识 Session

SQLAlchemy 的 Session 不是浏览器登录 Session,也不是一条永久占用的数据库连接。它表示一段数据库工作过程,把本次业务操作涉及的 ORM 对象、SQL 和数据库事务组织在一起。它主要负责:

  • 跟踪当前加载、新增、修改和删除的 ORM 对象;
  • 把对象变化转换成 SQL;
  • 管理当前事务;
  • 在需要时从连接池取得连接;
  • 提交、回滚并释放事务资源。

异步项目使用 AsyncSession,并通过工厂创建:

app/db/session.py
1
from sqlalchemy.ext.asyncio import async_sessionmaker
2
3
from app.db.engine import engine
4
5
6
session_factory = async_sessionmaker(
7
engine,
8
expire_on_commit=False,
9
)

async_sessionmaker 保存 Engine 和 Session 的统一配置,每次调用 session_factory() 都会创建一个新的 AsyncSession。工厂本身可以作为应用级对象复用,具体 Session 则通常与一次 Web 请求或后台任务的工作范围一致,不能把同一个实例放在全局变量中共享。

可以先用下面的关系理解这些对象:

session-scope.txt
1
应用进程
2
-> 复用 Engine 和连接池
3
-> session_factory 创建多个 AsyncSession
4
-> 每个 AsyncSession 管理自己的对象状态和事务
5
-> 执行 SQL 时从连接池取得连接

Session 在真正需要执行 SQL 时才会从 Engine 的连接池取得连接。事务提交或回滚后,连接会归还连接池;关闭 Session 还会移除其中跟踪的 ORM 对象,但不会销毁 Engine。也就是说,Session 和连接并不是同一个对象,创建 Session 也不等于立即建立一条新的数据库连接。

async with session_factory() as session: 只保证退出时关闭 Session,不会因为代码正常结束就自动提交。如果仍有未提交事务,关闭过程中会回滚。需要自动提交和回滚时,还要使用 session.begin(),或者使用后文介绍的 session_factory.begin() 组合上下文。

2. 对象状态

ORM 对象会随着 Session 操作进入不同状态。理解这些状态,可以解释为什么 add() 没有立即执行 INSERT、为什么 flush 后能拿到主键,以及为什么 Session 关闭后不能继续懒加载关系。

我们先假设下面的代码运行在一个已经取得 session 的业务函数中,并创建一个普通 Python 对象:

object-states.py
1
task = Task(
2
project_id=1,
3
task_number=1,
4
title="理解 SQLAlchemy Session",
5
status="todo",
6
priority=2,
7
)

此时它是 transient:只存在于 Python 内存,还不属于任何 Session。

加入 Session 后变成 pending:

object-states.py
1
session.add(task)

Session 知道它需要被插入,但 add() 本身通常不会立刻执行网络 I/O。

执行 flush() 后,INSERT 被发送到数据库,对象进入 persistent:

object-states.py
1
await session.flush()
2
print(task.id)

由 Identity 生成的主键可以在 flush 后回填到对象上,但事务还没有提交。此时其他事务通常看不到这行,当前事务仍然可以回滚。

从数据库查询出的对象也会直接处于 persistent 状态。Session 内部使用 identity map 记录对象身份:同一个 Session 中,同一张表、同一个主键通常对应同一个 Python 对象。这保证了本次工作范围内的对象修改能够统一跟踪,但 identity map 不是通用查询缓存,重复执行查询仍然可能发送 SQL。

删除对象时还会经历 deleted 状态:

object-states.py
1
await session.delete(task)
2
await session.flush()

调用 delete() 后,Session 会先把对象列入待删除集合;flush 发出 DELETE 后,对象才进入 deleted。之后提交事务,它会变成 detached;如果事务回滚,它会回到 persistent。与不需要 await 的 add() 不同,AsyncSession.delete() 是异步方法,因为删除级联可能需要加载尚未取得的关系。

需要确认对象当前处于哪种状态时,不必凭感觉判断,可以使用 inspect():

inspect-object-state.py
1
from sqlalchemy import inspect
2
3
4
state = inspect(task)
5
print(state.transient)
6
print(state.pending)
7
print(state.persistent)
8
print(state.deleted)
9
print(state.detached)

Session 关闭时,已经 persistent 的对象会变成 detached。它仍然是 Python 对象,但不能再依赖原 Session 加载尚未取得的关系或过期字段;尚未 flush 的 pending 对象被移出 Session 后则会回到 transient 状态。把所有对象简单理解为“关闭后都变成 detached”并不准确。

把最常见的变化连起来,就是:

object-state-flow.txt
1
transient --add()--> pending --flush()--> persistent --Session 关闭--> detached
2
|
3
+--delete() + flush()--> deleted
4
| commit() -> detached
5
| rollback() -> persistent
正在验证登录状态
请稍候,验证完成后将继续显示文章内容