replace into会触发delete和insert两类触发器,因其本质是先删除再插入;而insert on duplicate key update通常只触发insert类触发器,行为更可控。

REPLACE INTO 会触发 DELETE 和 INSERT 两类触发器
因为 REPLACE INTO 不是原子更新,而是先按主键或唯一索引查到旧记录,然后执行 DELETE,再执行 INSERT。只要表上定义了对应类型的触发器,就会分别触发一次 BEFORE DELETE、AFTER DELETE、BEFORE INSERT、AFTER INSERT —— 总共最多四次,常见的是两次(比如只定义了 AFTER INSERT 和 AFTER DELETE)。
触发器类型和数量取决于你实际定义了哪些
不是所有触发器都会“一定触发两次”,关键看定义:
- 如果你只定义了
AFTER INSERT,那REPLACE INTO会触发它一次(在插入新行时) - 如果你同时定义了
AFTER DELETE和AFTER INSERT,那就会各触发一次,看起来就是“两次” - 如果还定义了
BEFORE类型,且逻辑里又写了同表 DML(比如在BEFORE DELETE里又INSERT),就可能引发递归触发——这不是REPLACE的锅,而是触发器自己写的有问题
ON DUPLICATE KEY UPDATE 触发次数更可控
对比之下,INSERT ... ON DUPLICATE KEY UPDATE 只会触发 INSERT 相关的触发器(BEFORE INSERT、AFTER INSERT),即使发生冲突并执行了 UPDATE 分支,也不会触发 UPDATE 类触发器(MySQL 不支持该语法触发 UPDATE 触发器)。
这意味着:
- 不会因
DELETE导致外键级联或子表数据丢失 - 不会重置
created_at等默认时间戳字段 - 自增 ID 不变,触发器调用次数稳定(通常仅 2 次:BEFORE + AFTER)
- 如果你依赖触发器做日志或状态同步,
ON DUPLICATE KEY UPDATE的行为更可预测
调试时别被“执行两次”的表象带偏
看到日志里某条记录对应两条触发日志,第一反应不应该是“触发器写错了”,而应确认:
-
SHOW CREATE TRIGGER看清到底定义了哪几种类型 - 检查是否真有
REPLACE INTO在执行(而不是你以为的INSERT IGNORE或普通INSERT) - 用
SELECT @@sql_log_bin确认是否在从库回放 binlog,造成“二次触发”假象 - 避免在触发器里写
SELECT或直接输出,改用写影子表方式记录NEW.id和触发时机
真正难排查的,是那些隐式递归或主从复制叠加导致的“看似两次、实为四次甚至更多”的情况。











