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(),否则可能得到错误结果。

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

相关推荐
怕浪猫2 小时前
RAG 面试 6 连问,从原理到优化全部覆盖
python·算法·面试
高洁012 小时前
具身智能中的世界模型训练
人工智能·python·深度学习·机器学习·transformer
无线通信科研笔记2 小时前
IEEE TVT 2026 论文精读与完整复现|相位误差如何重塑近场 RIS 的幅相响应
论文阅读·人工智能·python·算法·论文笔记
朝朝辞暮i3 小时前
VLA 系统学习第 1 课:VLA 到底在干什么?
人工智能·python·计算机视觉·vla
2601_962885723 小时前
如何用 Python 自动识别股票的支撑位与压力位?
开发语言·python
北冥有鱼被烹4 小时前
VCSEL全景解析:与光模块NPO CPO的关系、市场量化分析与产业链影响
python
caoerzhong4 小时前
中小企业上 WMS 该先上哪几块:JeeWMS 开源 Java 仓库管理系统的分批上线清单
java·python·开源
正在走向自律4 小时前
AI数据分析与可视化:从基础到应用实践
服务器·人工智能·python·机器学习·数据分析·pandas
databook5 小时前
从手动检查到自动监控:一个数据质量工作流的实现
后端·python·数据分析
北冥有鱼被烹7 小时前
砷化镓与磷化铟:光模块的地基——从化学键到AI缺货潮的底层逻辑
人工智能·python