SQLAlchemy教程:expire_on_commit不要设置为True

说明

创建 session 工厂对象时,代码如下:

async_session = async_sessionmaker(
    async_engine,           # 绑定数据库引擎
    class_=AsyncSession,    # 指定会话类,调用 async_session() 返回的类型
    expire_on_commit=False
)

关键

expire_on_commit 的默认值是 True,需要将 expire_on_commit 设置为 False

作用:执行 commit 后,会话中的 表模型对象 不过期,再次访问对象中的属性(即:表中的字段),不需要重新查询数据库

expire_on_commit 设置为 True

代码修改为:

async_session = async_sessionmaker(
    async_engine,           # 绑定数据库引擎
    class_=AsyncSession,    # 指定会话类,调用 async_session() 返回的类型
    expire_on_commit=True
)

或者

去掉 expire_on_commit 参数,默认值为 True

错误写法1

save() 函数

修改 save() 函数,执行 commit 后,再访问对象的属性,如:id,内容如下:

async def save(user: User):
    # AsyncSession 使用上下文管理器,自动关闭会话
    async with async_session() as session:
        try:
            session.add(user)
            # 成功执行才提交事务
            await session.commit()
            # commit 后,访问对象属性
            print("id:", user.id)
        except Exception as e:
            # 如果报错就回滚
            await session.rollback()
            raise e

main() 函数

async def main():
    # await init_table()

    # 添加数据
    user = User(username="lilei", password="123456")
    await save(user)

    # ✅关键:主动dispose释放全部连接,避免GC阶段触发报错
    await async_engine.dispose()

报错

原因

  1. await save(db, user) 内部执行 await db.commit()
  2. 默认 expire_on_commit=True,执行 commit() 后, user 对象标记为 expired 过期
  3. 读取 user.id,对象过期,SQLAlchemy 尝试自动 SELECT 刷新对象
  4. AsyncSession 不能同步执行数据库 IO,直接抛 MissingGreenlet 错误

解决

解决方法有若干种,这里用:增加 await db.refresh(user)

代码

async def save(user: User):
    # AsyncSession 使用上下文管理器,自动关闭会话
    async with async_session() as session:
        try:
            session.add(user)
            # 成功执行才提交事务
            await session.commit()
            # commit后再刷新模型对象,会自动查询
            await session.refresh(user)
            print("id:", user.id)
        except Exception as e:
            # 如果报错就回滚
            await session.rollback()
            raise e

执行测试

程序能够正确执行,但看控制台,发现 commit() 后,又再次执行查询了:

结论

如果将 expire_on_commit 设置为 True ,commit 后模型对象已经过期,不能再用。
再次访问对象属性(即:表中的字段值),为了避免报错,需要加额外代码,导致再次查询数据

所以:expire_on_commit 设置为 False

完整代码

import asyncio

from sqlalchemy import String, Integer
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

# 数据库链接
DB_URL = "mysql+aiomysql://root:root@127.0.0.1:3306/fastapi_std?charset=utf8mb4"

async_engine = create_async_engine(
    DB_URL,
    echo=True,          # True打印执行SQL,调试打开;生产关闭
    pool_size=5,        # 连接池大小
    max_overflow=10,    # 最大连接数量
)

"""
创建异步会话工厂,通过该会话工厂,创建session
好处:参数统一在这里配置,如:expire_on_commit
"""
async_session = async_sessionmaker(
    async_engine,           # 绑定数据库引擎
    class_=AsyncSession,    # 指定会话类,调用 async_session() 返回的类型
    expire_on_commit=False  # commit后,会话中的模型对象不过期,访问对象的属性,不需重新查询数据库
)

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "user"
    id: Mapped[int] = mapped_column(Integer(),primary_key=True,autoincrement=True,comment="主键")
    username: Mapped[str] = mapped_column(String(50),comment="")
    password: Mapped[int] = mapped_column(String(50),comment="密码")


async def init_table():
    async with async_engine.begin() as conn:
        # 删除旧表(测试用)
        # await conn.run_sync(Base.metadata.drop_all)
        # 创建表
        await conn.run_sync(Base.metadata.create_all)


async def save(user: User):
    # AsyncSession 使用上下文管理器,自动关闭会话
    async with async_session() as session:
        try:
            session.add(user)
            # 成功执行才提交事务
            await session.commit()
        except Exception as e:
            # 如果报错就回滚
            await session.rollback()
            raise e


async def main():
    # await init_table()

    # 添加数据
    user = User(username="lilei", password="123456")
    await save(user)
    print("id:", user.id)

    # ✅关键:主动dispose释放全部连接,避免GC阶段触发报错
    await async_engine.dispose()


if __name__ == "__main__":
    asyncio.run(main())

结论

如果将 expire_on_commit 设置为 True ,commit 后模型对象已经过期,不能再用。
再次访问对象属性(即:表中的字段值),为了避免报错,需要加额外代码,导致再次查询数据

所以:expire_on_commit 设置为 False


原文出处:/show_1GW3sWwNvAuY.html