REPLACE INTO本质是“删+插”,非更新:先按主键或唯一索引查找,存在则DELETE原行再INSERT新行,否则直接INSERT;导致自增ID可能变化、触发器执行两次、外键级联触发。

MySQL中用REPLACE INTO实现时间戳驱动的增量覆盖
REPLACE INTO本质是“删+插”,当表有主键或唯一索引时,遇到冲突会先DELETE旧行再INSERT新行。它适合时间戳字段不参与约束、但业务上以最新updated_at为准的覆盖场景。
常见错误是误以为REPLACE能按时间戳自动判断新旧——它不会看updated_at值大小,只依赖主键/唯一键是否冲突。所以必须确保id或business_key能唯一标识一条业务记录。
- 表必须定义主键或至少一个UNIQUE索引,否则REPLACE退化为普通INSERT
- 被覆盖的行会触发ON DELETE CASCADE(如果外键配置了),需确认级联影响
- 自增ID在DELETE+INSERT过程中会递增,可能造成ID空洞
- 示例:
REPLACE INTO orders (id, order_no, amount, updated_at) VALUES (123, 'ORD-001', 99.5, '2024-06-15 10:30:00');
前提是id是主键;若想按order_no去重,则需建UNIQUE(order_no)
PostgreSQL和SQL Server用MERGE实现条件化覆盖
MERGE比REPLACE更可控:能显式写“当匹配时更新,不匹配时插入”,且可基于任意条件(比如比较updated_at)决定是否执行UPDATE。
PostgreSQL 15+原生支持MERGE,早期版本需用INSERT ... ON CONFLICT DO UPDATE替代;SQL Server从2008起就支持标准MERGE语法。
- PostgreSQL示例(v15+):
MERGE INTO orders t USING (VALUES (123, 'ORD-001', 105.0, '2024-06-15 11:20:00')) AS s(id, order_no, amount, updated_at) ON t.order_no = s.order_no WHEN MATCHED AND s.updated_at > t.updated_at THEN UPDATE SET amount = s.amount, updated_at = s.updated_at WHEN NOT MATCHED THEN INSERT (id, order_no, amount, updated_at) VALUES (s.id, s.order_no, s.amount, s.updated_at);
- SQL Server中
MATCHED分支里不能直接引用源表别名(如s.updated_at)做比较,得用EXCLUDED(PostgreSQL)或子查询绕过 - MERGE语句是单次原子操作,但某些数据库(如Oracle)对目标表加锁范围更大,高并发下可能成瓶颈
INSERT … ON CONFLICT在PostgreSQL中替代MERGE的实用写法
如果你用的是PostgreSQL 9.5–14,或者不想写MERGE这种稍重的语法,INSERT ... ON CONFLICT DO UPDATE更轻量、更常用,也支持时间戳条件判断。
关键点在于:冲突目标必须是主键或唯一约束列,而UPDATE动作可以加上WHERE条件来避免无效覆盖。
- 典型写法:
INSERT INTO orders (id, order_no, amount, updated_at) VALUES (123, 'ORD-001', 110.0, '2024-06-15 12:00:00') ON CONFLICT (order_no) DO UPDATE SET amount = EXCLUDED.amount, updated_at = EXCLUDED.updated_at WHERE EXCLUDED.updated_at > orders.updated_at;
-
EXCLUDED代表本次要插入的那行数据,不是关键字,不能加引号 - WHERE条件必须写在DO UPDATE之后,否则语法报错:
ERROR: syntax error at or near "WHERE" - 如果没加WHERE,每次冲突都会无条件更新,失去“仅覆盖更新”的语义
时间戳覆盖逻辑容易被忽略的边界问题
真正上线后出问题的,往往不是语法,而是时间戳本身不可靠:时区混乱、应用层生成时间不准、DB服务器与应用服务器时间不同步、批量导入时多条记录updated_at相同。
- 不要依赖应用传入的
updated_at做唯一判断——建议数据库用CURRENT_TIMESTAMP或NOW()生成,或至少校验其合理性(如不能早于30分钟前) - 当两条记录
updated_at完全相同时,覆盖逻辑失效,可能产生非预期结果;此时应引入第二排序字段,比如version或event_id - 部分ETL工具(如Airflow + psycopg2)默认关闭自动提交,
ON CONFLICT语句若不在事务块内执行,可能因中断导致部分成功部分失败 - 如果表有触发器,REPLACE或MERGE触发的DELETE/INSERT/UPDATE行为都可能激活它们,务必测试副作用










