SQLAlchemy 更新操作:values() 与 ordered_values() 的隐藏差异

1. 问题

摘要 :SQLAlchemy 的 values()ordered_values() 在普通更新时表现一致,但当某一列需要引用另一列的当前值时,两者生成的 SQL 在 SET 子句字段顺序上存在隐藏差异。values() 内部会按列名排序,无法控制顺序;而 ordered_values() 严格保留传入顺序。若列间存在相互引用,必须使用 ordered_values() 才能保证求值顺序正确,否则可能得到错误结果。

在 SQLAlchemy 中,Update 对象提供了 values()ordered_values() 两种方式来指定要更新的字段。大多数时候它们看起来没什么差别,都是一样的更新,似乎只有数据提供方式的不同。但当你需要让某一列引用另一列的当前值时,两者会暴露出一个容易被忽略的隐藏差异。

先看一张简单的记录表结构:

复制代码
class RecordPO(BasePO):
    """
    记录表
    """
    __tablename__ = "sys_record"
    id = Column("id", Integer, comment="主键", autoincrement=True, primary_key=True)
    name = Column("name", String(64), comment="名称")
    state = Column("state", String(64),   comment="状态")
    previousStatus = Column("previous_status", String(64))
    ipAddress = Column("ip_address", String(64))
    webBrowser=Column[str]("web_browser",String(255))
    message = Column("message", String(255))

update_by_values = Update(RecordPO).where(RecordPO.id == 1).values({RecordPO.name:"test_1",RecordPO.state:"failed"})
update_by_ordered= Update(RecordPO).where(RecordPO.id == 1).ordered_values((RecordPO.name,"test_1"),(RecordPO.state,"failed"))

但是如果需要这样更新:

复制代码
update_by_values = Update(RecordPO).where(RecordPO.id == 1).values({RecordPO.previousStatus:RecordPO.state,RecordPO.state:"failed"})
>UPDATE sys_record SET state=%s, previous_status=sys_record.state WHERE sys_record.id = %s 

update_by_ordered= Update(RecordPO).where(RecordPO.id == 1).ordered_values((RecordPO.previousStatus,RecordPO.state),(RecordPO.state,"failed"))
>UPDATE sys_record SET previous_status=sys_record.state, state=%s WHERE sys_record.id = %s 

不知道各位是否看出了问题?

2. 问题剖析:为什么 SQL 顺序会不同?

对比上面两条 SQL,核心差异在于 SET 子句中字段的排列顺序

  • 使用 values() 时,生成的 SQL 是 SET state=%s, previous_status=sys_record.state,即 state 在前、previous_status 在后;
  • 使用 ordered_values() 时,生成的 SQL 是 SET previous_status=sys_record.state, state=%s,即 previous_status 在前、state 在后。

这个顺序差异在绝大多数数据库上看似无关紧要,因为 SQL 标准并不保证 SET 子句的求值顺序。但问题恰恰出在这里:当某一列的赋值引用了另一列的当前值时,数据库实际执行时可能按 SET 子句的书写顺序逐列求值

values() 生成的 SQL 为例:

sql 复制代码
UPDATE sys_record SET state=%s, previous_status=sys_record.state WHERE sys_record.id = %s

如果数据库按从左到右的顺序执行,那么 state 先被更新为 "failed",随后 previous_status 再取 sys_record.state 时,拿到的已经是更新后的 "failed",而不是更新前的原始状态。这会导致 previous_status 被错误地写成 "failed",丢失了原始状态。

ordered_values() 生成的 SQL 把 previous_status 放在前面:

sql 复制代码
UPDATE sys_record SET previous_status=sys_record.state, state=%s WHERE sys_record.id = %s

此时 previous_status 先取 sys_record.state 的当前值(即更新前的原始状态),随后 state 才被更新为 "failed",从而正确保留了原始状态。

3. 为什么 ordered_values() 能保证顺序?

ordered_values() 的设计初衷,就是显式保留调用者传入的字段顺序 。它接收一个二元组列表,每个二元组是 (列, 值),SQLAlchemy 会严格按照这个列表的顺序生成 SET 子句。

values() 接收的是一个字典。在 Python 3.7+ 中,字典虽然保持插入顺序,但 SQLAlchemy 在内部处理时,会先对字典的键做一次排序(按列名或属性名的字母序),再生成 SQL。因此 values() 生成的 SET 子句顺序,并不一定等于你书写字典时的顺序,而是经过排序后的结果。

这正是两者最本质的区别:

方法 参数形式 SET 子句顺序 适用场景
values() 字典 按列名排序,不保证书写顺序 普通字段更新,无列间引用
ordered_values() 二元组列表 严格按传入顺序 列间存在相互引用时

4. 实际影响:什么时候必须用 ordered_values()?

当更新语句中某一列的值引用了另一列的当前值 时,就必须使用 ordered_values() 来保证求值顺序正确。典型场景包括:

  • 状态流转:把 previous_status 设为当前 state,再把 state 更新为新值;
  • 计数器累加:counter = counter + 1 这类自增操作;
  • 字段互换:交换两列的值,如 a = b, b = a

反之,如果只是普通的多字段更新,各列之间互不引用,那么 values()ordered_values() 的结果完全一致,用哪个都行。

5. 总结

  • values() 适合普通更新,内部会对字段排序,无法控制 SET 子句顺序;
  • ordered_values() 严格保留传入顺序,适合列间存在引用关系的更新;
  • 当某一列赋值依赖另一列当前值时,务必使用 ordered_values(),否则可能得到错误结果。

希望这篇文章能帮你避开这个隐藏的坑。如果你也遇到过类似问题,欢迎在评论区交流。

相关推荐
statistican_ABin1 小时前
中国互联网与数字生活分析报告—基于1990-2023年世界银行互联网数据的多维度分析
python
“AI国潮设计-小江”1 小时前
【Python/SDXL实战】潮汕国潮IP视觉落地:普宁美食猫IP & 创意甜品设计(附ComfyUI工作流思路)
开发语言·人工智能·python·prompt·aigc
Allen_LVyingbo2 小时前
医疗人工智能项目全生命周期管理系统:监管知识建模、工程实现与实证评估(下)
大数据·数据库·人工智能·python·自然语言处理·自动化
wuhuhuan2 小时前
Case Generator 平台升级
python·测试工具·自动化
RobinDevNotes2 小时前
用 Profile 揪出大模型训练性能瓶颈
python·性能优化
李航19832 小时前
用 DeepDraw 几何引擎开发建筑设计软件(六):创建画线工具
python·软件构建
AI 编程助手GPT2 小时前
Python 备份 SQLite:为什么复制了 .db,恢复后还是少数据?
人工智能·python·ai·chatgpt
狗都不学爬虫_2 小时前
AI逆向 - 天御无感点击验证(补+纯)
爬虫·python·网络爬虫
529宝宝起名网3 小时前
用 Python 开发历史名字查询与起名灵感工具:从古籍人物数据库到名字文化故事生成
开发语言·前端·python