FastAPI结合SQLAlchemy(SQLite)执行更新操作时出现不符合预期的结果
你遇到的这个问题其实是SQLAlchemy的会话(Session)状态管理导致的,属于ORM里常见的“对象已被会话跟踪”的情况,我来给你一步步解释清楚:
问题场景回顾
你当前的执行流程是:
- 在
main.py里调用crud.getdata从数据库拿到userdata(此时num是50) - 直接修改
userdata.num = 10,但这时候你还没提交到数据库 - 把修改后的
userdata传给crud.updatedata,结果在这个函数里查询同一个uid的用户时,拿到的nuser居然已经是num=10的状态,而不是数据库里的50,最终计算后变成0,完全不符合你预期的50-10=40。
原因分析
SQLAlchemy的Session会跟踪所有从它里面查询出来的对象,当你在main.py里通过getdata拿到userdata后,这个对象就被当前的Session“托管”了。一旦你修改了这个对象的属性(比如userdata.num = 10),哪怕还没调用commit(),Session内部已经记录了这个修改。
这时候当你在同一个Session里再次查询同一个用户(updatedata里的db.query(...)),SQLAlchemy不会再去数据库里重新捞数据,而是直接返回Session已经托管的那个被修改过的对象——也就是你刚改成10的那个userdata,所以才会出现查询结果和数据库实际值不一致的情况。
解决办法
根据你的需求,有几种可行的处理方式:
方式1:分离操作数据与查询对象(推荐)
如果你只是想基于数据库原始值做计算,不要先修改查询出来的对象,而是把要操作的数值单独传进去,避免会话跟踪的影响:
# 修改后的main.py @app.post("/test/{user}") def cal(user: int, subtract_num: int, db: Session = Depends(get_db)): # 只传递用户uid和要减去的数值,而非修改后的对象 user = crud.updatedata(db, user_uid=user, subtract_num=subtract_num) return user # 修改后的crud.py def updatedata(db: Session, user_uid: int, subtract_num: int): nuser = db.query(model.User).filter(model.User.uid == user_uid).one_or_none() if not nuser: # 处理用户不存在的情况 raise HTTPException(status_code=404, detail="User not found") nuser.num -= subtract_num # 直接基于数据库原始值计算 db.commit() db.refresh(nuser) return nuser
方式2:将对象从会话中分离
如果你一定要在main.py里先修改对象再处理,可以用db.expunge()把对象从Session的跟踪中移除,这样后续查询就会重新从数据库获取数据:
# 修改后的main.py @app.post("/test/{user}") def cal(user: int, db: Session = Depends(get_db)): userdata = crud.getdata(db, user=user) userdata.num = 10 # 解除会话对该对象的跟踪 db.expunge(userdata) user = crud.updatedata(db, userdata) return user
这种方式会增加逻辑复杂度,一般不推荐优先使用。
方式3:强制查询数据库最新值
如果你需要确保查询到的是数据库的原始值,可以在查询时加上populate_existing(),强制忽略Session缓存的对象:
# 修改crud.py里的查询语句 nuser = db.query(model.User).filter(model.User.uid == user.uid).populate_existing().one_or_none()
总结
这不是你代码的“错误”,而是SQLAlchemy Session的**一级缓存(First-Level Cache)**特性导致的预期外行为——Session会缓存它查询过的对象来提升性能,但这也会导致在同一个会话内修改对象后,后续查询拿到的是修改后的缓存对象,而非数据库里的原始值。
备注:内容来源于stack exchange,提问作者user27208323

