它只在插入时触发主键或任意唯一索引冲突才执行update;要求表必须有primary key或unique约束,普通索引无效;update中不可用where,但可用values(col)引用插入值;影响行数返回1(新插入)、2(更新且值变)、0(更新但值未变)。

MySQL 的 INSERT ... ON DUPLICATE KEY UPDATE 怎么写
MySQL 不支持标准 SQL 的 UPSERT 语法,但用 INSERT ... ON DUPLICATE KEY UPDATE 能实现冲突更新。前提是表里必须有 PRIMARY KEY 或 UNIQUE 约束,否则不会触发“冲突”逻辑。
常见错误是只加了普通索引(INDEX)却没设 UNIQUE,结果语句执行成功但始终不更新——因为 MySQL 根本没检测到冲突。
INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'a@example.com') ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email)-
VALUES(col)是关键:它取的是本次INSERT中该列的原始值,不是当前行旧值 - 如果想在更新时做计算(比如计数器+1),写成
count = count + 1,不能用VALUES(count) + 1
PostgreSQL 的 INSERT ... ON CONFLICT DO UPDATE 怎么配 target
PostgreSQL 的 ON CONFLICT 必须显式指定冲突目标,不能省略。最常漏掉的是 ON CONFLICT (column_name) 里的括号和列名——写成 ON CONFLICT column_name 会直接报错:ERROR: syntax error at or near "column_name"。
如果你的唯一约束是复合的(比如 (user_id, category)),那 ON CONFLICT 也得写全:ON CONFLICT (user_id, category),少一个都不行。
- 要更新整行,用
DO UPDATE SET name = EXCLUDED.name, updated_at = NOW() -
EXCLUDED是 PostgreSQL 的保留字,代表本次想插入但被拦下的那行数据 - 不能在
SET子句里直接引用原表别名(如users.name),会报错;要用EXCLUDED或子查询
SQLite 的 INSERT OR REPLACE 为什么可能删数据
INSERT OR REPLACE 看似简单,但它底层是「删+插」:先按唯一键找到旧行并删除,再插入新行。这意味着——
- 如果表有
ON DELETE CASCADE外键,关联记录会被连带删掉 - 自增主键(
INTEGER PRIMARY KEY)会分配新 ID,旧 ID 永久丢失 - 触发器(
AFTER DELETE)会被执行,可能引发意外副作用
真正安全的替代方案是 INSERT OR IGNORE + 单独 UPDATE,虽然多一条语句,但行为可控。例如:
INSERT OR IGNORE INTO users (id, name) VALUES (1, 'Bob');<br>UPDATE users SET name = 'Bob' WHERE id = 1;
跨数据库兼容写法为什么很难落地
没有银弹。MySQL 用 ON DUPLICATE KEY UPDATE,PostgreSQL 用 ON CONFLICT,SQLite 用 REPLACE 或分步操作,SQL Server 用 MERGE——语法、语义、锁行为全都不一样。
ORM 层(如 SQLAlchemy、Django ORM)封装了部分差异,但一旦涉及复杂条件(比如只在 updated_at 更旧时才更新),就得退回到原生语句,这时兼容性就彻底失效。
最容易被忽略的一点:各数据库对“冲突判断时机”的实现不同。MySQL 在语句执行末尾统一检查,PostgreSQL 在插入过程中逐行检查,这会影响并发场景下的最终结果。真要强一致性,得靠应用层加锁或重试逻辑。










