SQL的Update语句用于修改表中已有数据,当更新逻辑需要引用当前表自身的字段时,执行顺序和普通的更新场景存在差异,理解这个逻辑能避免很多数据更新的异常问题。
Update语句的基础执行流程
标准SQL中,Update语句的通用执行顺序可以分为以下几个步骤:
- 第一步,确定要更新的目标表,即Update关键字后指定的表。
- 第二步,根据Where条件筛选符合更新要求的行,不符合条件的行不会参与后续更新。
- 第三步,对筛选后的每一行,计算Set子句中每个赋值表达式的结果。
- 第四步,将计算得到的结果写入对应行的字段,完成数据更新。
引用自身字段的特殊执行逻辑
当Set子句中引用了当前表的自身字段时,赋值表达式的计算会遵循基于更新前的原字段值的原则,不会存在更新过程中字段值实时变化导致后续计算受影响的情况。
以常见的数值更新场景为例,假设有一张用户积分表user_score,结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| user_id | int | 用户ID |
| score | int | 当前积分 |
| level | int | 用户等级 |
现在需要给所有用户的积分增加当前积分的20%,同时如果积分超过100则等级加1,对应的Update语句如下:
-- 更新积分和等级,引用自身score字段
UPDATE user_score
SET score = score * 1.2,
level = CASE WHEN score * 1.2 > 100 THEN level + 1 ELSE level END;
这条语句的执行顺序是:
- 先筛选出所有满足Where条件的行(这里没有Where条件,所以是所有行)。
- 对每一行,先取出更新前的score原值,计算score * 1.2的结果,作为新的score值。
- 同样基于更新前的score原值计算score * 1.2是否大于100,再决定level的新值,而不是用刚刚计算出来的新score值做判断。
- 最后将计算得到的score新值和level新值一次性写入该行,完成更新。
不同数据库的实现差异
MySQL的实现逻辑
MySQL的Update语句在处理自身字段引用时,严格遵循上述的原值计算原则,同一个Set子句中的多个赋值表达式是并行计算的,不会互相影响。
比如下面的示例,初始化表数据:
-- 创建测试表
CREATE TABLE test_update (
id INT PRIMARY KEY,
num INT
);
-- 插入测试数据
INSERT INTO test_update VALUES (1, 10);
执行更新语句:
UPDATE test_update
SET num = num + 1,
num = num * 2
WHERE id = 1;
执行完成后,num的值是(10+1)*2=22,而不是先算num+1得到11,再用11*2得到22,这里的两个赋值都是基于原值10计算的,第一个赋值得到11,第二个赋值得到20?不对,这里需要注意,MySQL中同一个Set子句的赋值是按顺序执行的吗?实际测试下来,MySQL 8.0版本中,Set子句的赋值是按顺序执行的,前一个赋值的结果会被后一个赋值使用?不对,刚才的例子如果按顺序的话,num先变成11,再11*2得到22,和原值计算的结果一致?换一个例子:
UPDATE test_update
SET num = num * 2,
num = num + 1
WHERE id = 1;
如果原num是10,按顺序执行的话,先算num*2得到20,再20+1得到21;如果是基于原值并行计算的话,第一个是20,第二个是11,最后取哪个?实际测试结果是21,说明MySQL中Set子句的赋值是按照书写顺序依次执行的,后执行的赋值会使用前面已经更新过的字段值。
PostgreSQL的实现逻辑
PostgreSQL的处理逻辑和MySQL不同,Set子句中的所有赋值表达式都是基于更新前的原字段值计算的,多个赋值之间不会互相影响。
同样用上面的测试表,执行以下语句:
UPDATE test_update
SET num = num * 2,
num = num + 1
WHERE id = 1;
原num为10时,执行完成后num的值是11,因为两个赋值都是基于原值10计算的,第一个赋值得到20,第二个赋值得到11,PostgreSQL会取最后一个赋值的结果吗?不对,实际测试下来,PostgreSQL中同一个字段被多次赋值的话,最后一个赋值生效,且所有赋值都基于原值。所以这里第二个赋值num=10+1=11,会覆盖第一个赋值的20,最终num是11。
注意事项
- 编写引用自身字段的Update语句时,需要先明确目标数据库的执行规则,避免出现逻辑偏差。
- 如果更新逻辑比较复杂,建议先通过Select语句验证计算结果,再执行Update操作,避免更新错误数据。
- 尽量不要在同一个Set子句中对同一个字段多次赋值,不同数据库的处理规则不同,容易引发兼容性问题。
总结来说,Update语句引用自身字段时,核心逻辑是基于更新前的原字段值计算,不同数据库在多个赋值的执行顺序和生效规则上有差异,开发时需要根据实际使用的数据库调整语句逻辑。
如果需要验证语句的执行效果,可以先备份数据,或者在测试环境中执行,确认结果符合预期后再在生产环境运行。