INSERT IGNORE 静默跳过唯一冲突,不更新;REPLACE INTO 先删后插,会重置自增ID和未指定字段;ON DUPLICATE KEY UPDATE 才是安全更新指定字段的正确方案。

MySQL 插入重复数据时,INSERT IGNORE 和 REPLACE INTO 是最常用的两种“兜底”写法,但它们行为完全不同,选错会丢数据或破坏业务逻辑。
INSERT IGNORE 只跳过,不报错也不更新
当表中存在 PRIMARY KEY 或 UNIQUE 约束(比如 UNIQUE(username)),且你要插入的值已存在时,INSERT IGNORE 会静默跳过这一行,返回 0 rows affected,不抛异常,也不触发警告以外的任何反馈。
- 适合场景:补数据、导入日志、幂等写入(如定时同步用户基本信息)
- 常见错误现象:执行后查不到新数据,但也没报错——其实是被忽略了,得看
mysql_affected_rows()或 SQL 返回的rows affected值判断 - 注意点:
IGNORE不会抑制其他错误(如字段超长、类型不匹配),只对唯一性冲突有效 - 示例:
INSERT IGNORE INTO users (username, email) VALUES ('alice', 'a@b.com');—— 若username已存在,整条语句无效果
REPLACE INTO 先删后插,ID 和自增列会变
REPLACE INTO 的本质是:检测到主键或唯一键冲突时,**先执行 DELETE,再执行 INSERT**。这意味着:
- 原记录的自增 ID 会被释放,新插入的记录获得新的 ID(除非显式指定 ID)
- 外键关联的子表若设了
ON DELETE CASCADE,可能意外级联删除 - 触发器(
BEFORE DELETE/AFTER INSERT)会被完整触发两次 - 性能更重:一次冲突 = 1 次 delete + 1 次 insert,比
INSERT IGNORE多一次 I/O - 示例:
REPLACE INTO users (id, username, email) VALUES (1, 'bob', 'b@b.com');—— 若 id=1 已存在,原记录消失,新记录 id 可能仍是 1(取决于是否显式传入),但created_at等时间戳字段会重置
为什么不用 REPLACE INTO 处理用户资料更新?
很多开发者误以为 REPLACE INTO 是“有就更新”的快捷写法,但它根本不是更新,而是删除重建。这带来几个隐蔽风险:
- 丢失未在
VALUES中显式列出的字段值(比如你只写(id, username),那last_login、status全部变成默认值或 NULL) - 违反“只更新必要字段”的设计原则,容易引发数据一致性问题
- 无法做条件更新(例如“仅当 email 为空时才写入”),而
ON DUPLICATE KEY UPDATE可以 - 如果表没设主键或唯一索引,
REPLACE INTO就退化为普通INSERT,行为不可控
真正该用什么?优先考虑 ON DUPLICATE KEY UPDATE
如果你要的是“存在则更新指定字段”,ON DUPLICATE KEY UPDATE 才是语义准确、可控性强的方案。它只修改你明确列出的字段,不碰其他列,也不改变自增 ID。
- 必须确保冲突字段上有
PRIMARY KEY或UNIQUE索引,否则不生效 - 更新字段可引用
VALUES(col_name)获取本次插入值,比如:UPDATE name = VALUES(name), updated_at = NOW() - 支持表达式:
score = score + VALUES(score),适合计数类场景 - 示例:
INSERT INTO users (username, email) VALUES ('carol', 'c@c.com') ON DUPLICATE KEY UPDATE email = VALUES(email), updated_at = NOW();
唯一索引定义和冲突检测逻辑容易被忽略——比如联合唯一索引 UNIQUE(first_name, last_name),只有两个字段值同时相同时才算冲突;单字段索引漏建、NULL 值参与比较(MySQL 中多个 NULL 不视为重复)等问题,都会让上述所有语句“失效”。











