1. 认识 Session
SQLAlchemy 的 Session 不是浏览器登录 Session,也不是一条永久占用的数据库连接。它表示一段数据库工作过程,把本次业务操作涉及的 ORM 对象、SQL 和数据库事务组织在一起。它主要负责:
- 跟踪当前加载、新增、修改和删除的 ORM 对象;
- 把对象变化转换成 SQL;
- 管理当前事务;
- 在需要时从连接池取得连接;
- 提交、回滚并释放事务资源。
异步项目使用 AsyncSession,并通过工厂创建:
1from sqlalchemy.ext.asyncio import async_sessionmaker23from app.db.engine import engine456session_factory = async_sessionmaker(7engine,8expire_on_commit=False,9)
async_sessionmaker 保存 Engine 和 Session 的统一配置,每次调用 session_factory() 都会创建一个新的 AsyncSession。工厂本身可以作为应用级对象复用,具体 Session 则通常与一次 Web 请求或后台任务的工作范围一致,不能把同一个实例放在全局变量中共享。
可以先用下面的关系理解这些对象:
1应用进程2-> 复用 Engine 和连接池3-> session_factory 创建多个 AsyncSession4-> 每个 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 对象:
1task = Task(2project_id=1,3task_number=1,4title="理解 SQLAlchemy Session",5status="todo",6priority=2,7)
此时它是 transient:只存在于 Python 内存,还不属于任何 Session。
加入 Session 后变成 pending:
1session.add(task)
Session 知道它需要被插入,但 add() 本身通常不会立刻执行网络 I/O。
执行 flush() 后,INSERT 被发送到数据库,对象进入 persistent:
1await session.flush()2print(task.id)
由 Identity 生成的主键可以在 flush 后回填到对象上,但事务还没有提交。此时其他事务通常看不到这行,当前事务仍然可以回滚。
从数据库查询出的对象也会直接处于 persistent 状态。Session 内部使用 identity map 记录对象身份:同一个 Session 中,同一张表、同一个主键通常对应同一个 Python 对象。这保证了本次工作范围内的对象修改能够统一跟踪,但 identity map 不是通用查询缓存,重复执行查询仍然可能发送 SQL。
删除对象时还会经历 deleted 状态:
1await session.delete(task)2await session.flush()
调用 delete() 后,Session 会先把对象列入待删除集合;flush 发出 DELETE 后,对象才进入 deleted。之后提交事务,它会变成 detached;如果事务回滚,它会回到 persistent。与不需要 await 的 add() 不同,AsyncSession.delete() 是异步方法,因为删除级联可能需要加载尚未取得的关系。
需要确认对象当前处于哪种状态时,不必凭感觉判断,可以使用 inspect():
1from sqlalchemy import inspect234state = inspect(task)5print(state.transient)6print(state.pending)7print(state.persistent)8print(state.deleted)9print(state.detached)
Session 关闭时,已经 persistent 的对象会变成 detached。它仍然是 Python 对象,但不能再依赖原 Session 加载尚未取得的关系或过期字段;尚未 flush 的 pending 对象被移出 Session 后则会回到 transient 状态。把所有对象简单理解为“关闭后都变成 detached”并不准确。
把最常见的变化连起来,就是:
1transient --add()--> pending --flush()--> persistent --Session 关闭--> detached2|3+--delete() + flush()--> deleted4| commit() -> detached5| rollback() -> persistent