INSERT ... ON CONFLICT 把竞态收进一条语句
INSERT … ON CONFLICT 让 PostgreSQL 在真正写入的语句内部处理插入冲突。它用明确的冲突策略和实际索引或约束,替代脆弱的“先查键是否存在,再决定插入”流程。但它不会把所有插入错误都变成更新成功,也不会替应用决定新旧数据应该如何合并。使用前,需要先确定唯一键和业务动作。 冲突目标定义处理范围 (name) 这样的目标,要求 PostgreSQL 推断匹配的唯一索引。表上必须实际存在适用的唯一性规则;写出 ON CONFLICT 并不会自动建立约束。如果键涉及表达式或部分索引,索引表达式和谓词也会影响匹配。目标没有匹配到索引时会报错,而不是悄悄改用另一种插入策略。 DO UPDATE 必须指定目标,其裁决依据只能是受支持的非延迟唯一约束或唯一索引,排除约束不能作为 DO UPDATE 的冲突裁决依据。DO NOTHING 的处理范围更广,包括受支持的排除约束,也可以省略目标来处理适用的冲突规则。但这些能力都不会让外键、非空或检查约束失效,不能把它当成通用的错误忽略机制。 原子的插入或更新行为,解决的是并发争用同一键时的协调。语句仍可能等待其他事务持有的相关行或索引冲突,正常的事务错误也仍可能发生。原子性描述结果如何被协调,不保证完全无锁、不阻塞,也不意味着调用方永远不用处理事务级重试。 明确写出合并规则 目标表名或别名代表已有行,EXCLUDED 代表原本准备插入的行,其中也反映 BEFORE INSERT 触发器的相关影响。赋值为 EXCLUDED.total 是替换旧值;赋值为已有 total 加 EXCLUDED.total 是累加一次贡献。虽然语法相近,两者回答的业务问题不同,不能把其中一种默认当成“正确的 upsert”。 计数器直接在更新表达式中使用已有行,可以避免客户端先读旧值,再算一个可能过时的替换值。带版本的文档则可以用 WHERE 拒绝较旧版本。这个条件在找到并锁定冲突行后求值,因此,即使最终没有更新,它也可能与其他写入者争用锁。条件跳过不等于完全没有执行成本或并发影响。 RETURNING 只输出真正被插入或更新的行。DO NOTHING 不会返回已经存在的那条记录;DO UPDATE 的 WHERE 为假时,也不会返回行。所以空结果不能唯一推断出“走了 DO NOTHING”,更不能直接认定数据库中没有该记录。调用方应明确如何解释被跳过的写入,而不是把所有零行返回都当成同一种错误。 ...