SQLite 同条 UPDATE 的取值时机:x 已经加一,为什么 y 仍然拿到旧的 x
想让x增加一,再把新x写进y,把两项放进同一条UPDATE的SET列表后,却发现y得到旧x。SQLite针对被更新行的列引用,会先求出右侧标量表达式,再实施赋值。SET中从左到右的书写位置,不能当作逐行执行的脚本步骤。
下面使用Python标准库sqlite3建立独立内存数据库。保存为demo.py并运行python3 demo.py,已在CPython 3.12.14与SQLite 3.53.1验证。程序只有一条固定记录,所有更新都带id=1条件;关闭连接后数据消失,不会接触已有数据库。
AI模型生成概念图:上方原始行中的两个物件交叉进入下方新行,表示新值共同来自更新前的状态;不是数据库内部执行计划。
完整实验程序
每轮先恢复x=10、y=20,随后只改变赋值的组织方式。read从同一连接取回结果,断言固定为一对整数,避免触发器、并发连接或显示格式干扰这次取值时机实验。
完整可运行程序
import sqlite3
db = sqlite3.connect(':memory:')
try:
db.execute('CREATE TABLE pair(id INTEGER PRIMARY KEY, x INTEGER, y INTEGER)')
db.execute('INSERT INTO pair VALUES(1, 10, 20)')
def reset():
db.execute('UPDATE pair SET x=10, y=20 WHERE id=1')
def read():
return db.execute('SELECT x, y FROM pair WHERE id=1').fetchone()
db.execute('UPDATE pair SET x=x+1, y=x WHERE id=1')
assert read() == (11, 10)
print('same statement:', read())
reset()
db.execute('UPDATE pair SET y=x, x=x+1 WHERE id=1')
assert read() == (11, 10)
print('reversed SET order:', read())
reset()
db.execute('UPDATE pair SET x=y, y=x WHERE id=1')
assert read() == (20, 10)
print('single statement swap:', read())
reset()
db.execute('UPDATE pair SET x=y WHERE id=1')
db.execute('UPDATE pair SET y=x WHERE id=1')
assert read() == (20, 20)
print('two statements:', read())
finally:
db.close()本次实际输出
same statement: (11, 10) reversed SET order: (11, 10) single statement swap: (20, 10) two statements: (20, 20)
先计算右侧,再写入新行
same statement得到(11, 10)。x=x+1用旧十计算出十一;y=x也从同一条原始行读到十。不能把这一行理解成先完成x的写入,再让y读刚写进去的十一。
第二轮把SET里的两项调换位置,仍得到(11, 10)。这个对照排除了“把y写在后面就能看到新x”的修复方向。若业务要求y就是新x,可以在两处明确写出所需表达式,或者先在应用层计算本次要写入的两个目标值。
同一规则能直接支持两列交换
single statement swap得到(20, 10)。x=y取旧二十,y=x取旧十,两项因此完成交换,不需要在表里临时加一个中转列。这里两列都是允许这些整数值的普通列,交换没有引入额外类型或约束冲突。
交换成功的前提也应进入测试。如果一列有独立的CHECK约束或数据类型要求,换来的值未必合法。若对象有触发器,更新还可能产生额外变化;本文刻意没有触发器,因此观察到的是最小的SET列引用规则。
拆成两条语句会改变观察到的状态
最后一轮先执行x=y,此时行变成(20, 20);第二条y=x再从已经更新后的行读取二十,最终仍是(20, 20)。丢失的旧十不会因为这两条语句处在同一个连接或事务里自动回来。
事务可以组织提交与回滚边界,却不会把第二条语句读取的值还原到第一条之前。要拆步骤实现交换,就必须明确保存旧值;如果适合同条赋值,直接使用已经验证的单条交换更容易表达意图。
别把行内规则扩展成所有SQL的求值顺序
本文验证的是UPDATE中引用当前被更新行列值的右侧标量表达式。它没有证明自定义函数调用次数、相关子查询读取其他行、UPDATE FROM多行匹配或触发器执行顺序。那些场景应按各自规则建立独立实验,不能从这四行结果推导出通用顺序保证。
真实更新前应核对WHERE能锁定预期记录。没有WHERE会扩大到所有行,而条件未匹配也不一定抛错。这个教学程序每轮都用固定主键读回结果,业务程序也应验证受影响记录与最终数据,而不只检查execute有没有返回异常。
修改跨数据库应用时,应重新查目标引擎的赋值语义,避免把SQLite的这项保证当作所有SQL实现都相同。保留这里的交换与顺序对照样本,可以帮助发现移植后行为是否发生变化。
参考资料
官方资料核验日期:2026年10月2日。上述输出来自本文完整程序的本地执行,断言全部通过,退出状态为零。


