评审了新迭代的需求,翻以前功能的代码,看到了一个ON DUPLICATE KEY UPDATE的语句,有点不对劲。

实际数据库设计业务相关内容,这里不写,单独抽象出来一个场景如下

有一张表table_c,主键id无关紧要,其中有一个唯一键索引req_id,这个索引保证了表中每行的req_id都是唯一的

create table table_c
(
    id          int auto_increment
        primary key,
    req_id      int           null,
    req_type    int           null,
    req_version int default 0 null,
    constraint table_c_req_id_uindex
        unique (req_id)
);

需求场景是这样的,从第三方拉取到数据,对应唯一的req_id,如果表中没有则插入一条新数据。如果表中有,则判断req_version字段,只有当新数据的req_version比原表中的大的时候才更新整行数据。于是根据这个需求场景来原先的代码中的SQL如下

INSERT INTO table_c (req_id, req_type, req_version)
VALUES (124, 4, 1)
ON DUPLICATE KEY UPDATE 
    req_version = IF(VALUES(req_version) > req_version, VALUES(req_version), req_version),
    req_type    = IF(VALUES(req_version) > req_version, VALUES(req_type), req_type)

乍一看起来使用了ON DUPLICATE KEY UPDATE,也使用了IF判断了req_version字段,好像没毛病。但是实际上是有问题的,我们跑一个实际数据看下

表中现有数据

执行上面的SQL,按照预期req_type应该会被更新为4,同时req_version被更新为1,执行一下

可以看到执行成功,而表中现在的结果如下

req_version确实更新了但是req_type却没有更新,那么我们可以推断出req_type后面的IF语句没有获得返回结果true,那么可能的情况就是,在前面先执行

req_version = IF(VALUES(req_version) > req_version, VALUES(req_version), req_version)

这一段的时候req_version的值已经被更新为1了,且VALUES(req_version)的值也是1,那么在后面再执行VALUES(req_version) > req_version判断的时候就会得到false

不妨改变下SQL再试下

在UPDATE语句部分直接填上req_version的值,注意不是VALUES(req_version)的值

表中结果如下

可以确定的看到在执行到req_type这行的时候req_version的值确实变成2了。

根据这样的结论,我们再次调整下SQL语句,执行查看结果

将用于判断的req_version字段的赋值移动到最后一行

可以看到结果

最后,结论

用于判断的更新的字段的取值需要放在对应判断条件使用的后面,否则会引发更新取值数据错误的问题。