INSERT IGNORE 静默跳过唯一冲突不报错,mysql_affected_rows()返回0易误判失败;REPLACE INTO 实为DELETE+INSERT,会变更自增ID、触发两次触发器;ON DUPLICATE KEY UPDATE 才是安全可控的冲突更新方案。

INSERT IGNORE 为什么有时“静默失败”却没报错
它不是真失败,而是把违反唯一约束的行直接跳过,连警告都不抛——但mysql_affected_rows()会返回 0,容易误判为“没插入成功”。尤其在批量插入时,你可能以为数据全进去了,其实部分被忽略了。
常见错误现象:Duplicate entry 'xxx' for key 'uk_name' 这类警告被吞掉,日志里看不到,监控也抓不到。
- 只适用于你想“有就跳过、没有才插”的场景,比如幂等写入缓存表或日志去重
-
INSERT IGNORE不会触发ON DUPLICATE KEY UPDATE,也不能改已有数据 - MySQL 5.7+ 默认开启
STRICT_TRANS_TABLES时,某些隐式类型转换冲突仍可能报错,不是所有“重复”都静默 - 注意:它对主键和所有唯一索引生效,哪怕你只关心某一个字段重复
REPLACE INTO 实际是 DELETE + INSERT,别当 UPDATE 用
REPLACE INTO 看着像“存在就替换”,但底层先按唯一键查,找到就删原行再插新行。自增 ID 会变、外键关联可能中断、触发器会执行两次(DELETE 和 INSERT 各一次)。
使用场景有限:仅当你明确需要“强制覆盖整行且不介意 ID 变更/触发器副作用”时才考虑。
- 如果表有自增主键,
REPLACE INTO后新记录的 ID 通常会变大,老 ID 永久丢失 - 外键设了
ON DELETE CASCADE?那被 REPLACE 的行会连带删掉子表数据 - 性能差:比
INSERT ... ON DUPLICATE KEY UPDATE多一次 I/O,还多一次索引查找 - 不支持部分字段更新——哪怕你只改一个字段,也是整行重建
ON DUPLICATE KEY UPDATE 才是真正可控的“冲突处理”
它才是真正意义上的“存在则更新”,不删不插,只改指定字段。语义清晰、性能好、ID 和外键关系都保留。
参数差异关键点:更新目标必须是已存在的唯一键所对应的行,不能跨唯一索引匹配(比如用 A 字段唯一索引匹配,却想更新 B 字段唯一索引下的另一行)。
- 语法是
INSERT ... ON DUPLICATE KEY UPDATE col1=VALUES(col1), col2='fixed' -
VALUES(col1)表示本次 INSERT 语句中给col1指定的值,不是当前行旧值 - 可以更新多个字段,也可以用表达式,比如
count = count + 1 - 如果唯一冲突来自联合唯一索引(如
(user_id, day)),只有整组值完全相同时才触发 UPDATE
唯一键冲突时,事务回滚行为取决于你选哪个语句
很多人以为加了 BEGIN; ... COMMIT; 就万事大吉,但不同语句在事务里的表现差异很大。
比如 INSERT IGNORE 遇到冲突只是跳过,不影响事务状态;而 REPLACE INTO 或 ON DUPLICATE KEY UPDATE 都算正常执行,事务继续有效。但如果你混用这些语句又没检查返回值,可能误以为某条语句失败导致整个事务该回滚,其实它根本没报错。
-
INSERT IGNORE在事务中不会自动回滚,也不会中断事务,但你要自己判断mysql_affected_rows()是 0 还是 1 -
ON DUPLICATE KEY UPDATE影响行数可能是 2(新插入)或 0(冲突后更新零个字段)或 1(冲突后更新至少一个字段),得看清楚逻辑 - 想严格保证“要么全插入、要么全失败”,就别用 IGNORE 或 REPLACE,改用
SELECT ... FOR UPDATE加手动判断
唯一键冲突本身不等于事务失败,但怎么响应它,决定了你的数据是否可预期。最容易被忽略的是:不检查返回值,就默认语句“按你写的逻辑走完了”。











