SQLAlchemy更新仅在调试/打印对象时生效问题求助
问题
在FastAPI+SQLAlchemy开发的后端应用中,遇到如下异常场景:
- 用户提交问卷响应时,流程为:创建Response数据库对象 → 创建关联的Match对象 → 统计
number_correct_matches字段值 → 调用CRUD的update方法更新Response对象的该字段 - 正常运行时(本地PostgreSQL环境或单元测试SQLite内存库),数据库中的Response对象并未被更新;仅在两种场景下更新操作生效:
- VSCode调试模式下单步执行update函数的for循环代码
- 在update函数中添加打印
db_object的日志语句
- 应用中其他模型(如User)使用同一update函数可正常完成更新
- 当前代码运行日志仅输出
updating 1和updating 4,for循环内的日志未触发
相关代码片段
api/response.py
response = crud.response.update( session=session, db_object=response, # <- 已在数据库中创建的Response对象 object_in=schemas.ResponseUpdate( number_correct_matches=number_correct_matches ), )
schemas/response.py
# properties to receive via API update class ResponseUpdate(ResponseBase): number_correct_matches: int = 0
crud_base.py(由crud_response继承)
def update( self, session: Session, *, db_object: ModelType, object_in: Union[UpdateSchemaType, Dict[str, Any]] ) -> ModelType: """ Updates an object in the database Args: session (Session): a SQLAlchemy Session object that is connected to the database. db_object (ModelType): An object from the database. object_in (Union[UpdateSchemaType, Dict[str, Any]]): A pydantic model object used to update the db model object. Returns: ModelType: The updated db model object. """ # logger.info(f"updating {db_object}") <- 仅该日志语句存在时更新生效 logger.info("updating 1") # update the db object update_data = ( # <- 在此设断点直接跳至for循环外时更新失效 object_in if isinstance(object_in, dict) else object_in.model_dump(exclude_unset=True) ) for field in jsonable_encoder(db_object): # <- 在此设断点单步执行至循环结束时更新生效 logger.info("updating 2") if field in update_data.keys(): logger.info("updating 3") setattr(db_object, field, update_data[field]) logger.info("updating 4") # update the db object through the session session.add(db_object) # <- 在此设断点时更新失效 session.commit() session.refresh(db_object) return db_object
运行日志
2024-07-12 20:53:52.228 | INFO | app.crud.crud_base:update:82 - updating 1 2024-07-12 20:53:52.229 | INFO | app.crud.crud_base:update:94 - updating 4
根本原因与解决方案
根本原因
问题核心在于jsonable_encoder(db_object)的行为结合SQLAlchemy的懒加载机制:
- 刚创建的Response对象,其属性可能处于未加载的懒加载状态(未被显式访问过),此时
jsonable_encoder无法正确枚举对象的字段列表,导致for循环没有可迭代元素,直接跳过更新逻辑 - 调试单步执行或打印
db_object时,会隐式触发对象属性的加载(比如打印对象会访问其所有可序列化属性),此时jsonable_encoder能正确获取字段列表,for循环正常执行,更新生效 - 其他模型(如User)正常更新,是因为这些对象的属性在传入update函数前已被加载过,不存在懒加载未触发的情况
解决方案
替换jsonable_encoder(db_object)的字段枚举方式,直接从SQLAlchemy模型的表结构中获取字段名,彻底规避懒加载影响:
修正后的crud_base.py update函数核心代码:
def update( self, session: Session, *, db_object: ModelType, object_in: Union[UpdateSchemaType, Dict[str, Any]] ) -> ModelType: logger.info("updating 1") # 获取更新数据 update_data = ( object_in if isinstance(object_in, dict) else object_in.model_dump(exclude_unset=True) ) # 直接从模型表结构获取字段名,不受懒加载影响 for field in db_object.__table__.columns.keys(): logger.info("updating 2") if field in update_data: logger.info("updating 3") setattr(db_object, field, update_data[field]) logger.info("updating 4") session.commit() session.refresh(db_object) return db_object
额外说明:
- 可移除
session.add(db_object),SQLAlchemy会话会跟踪已存在的对象,修改后直接提交即可生效 - 若
update_data存在字段缺失问题(比如exclude_unset=True排除了需要更新的字段),可调整为object_in.model_dump()(不排除任何字段),或确保传入的Pydantic对象显式设置了目标字段
内容的提问来源于stack exchange,提问作者Daniel Auerbach
相关产品推荐
相关产品推荐

