根本原因是表上缺少primary key或unique约束;mysql仅在触发主键或唯一键冲突时才跳过插入,否则insert ignore与普通insert行为一致,必须用show create table确认索引存在且覆盖目标字段。

INSERT IGNORE 为什么有时没效果
根本原因是表上缺少 PRIMARY KEY 或 UNIQUE 约束。MySQL 的 INSERT IGNORE 只在遇到主键或唯一键冲突时才跳过插入,否则它和普通 INSERT 完全一样。
- 检查表结构:
SHOW CREATE TABLE table_name;,确认至少有一个UNIQUE字段(比如email、username或组合索引) - 如果业务上需要按某字段去重但该字段没建唯一索引,必须先加:
ALTER TABLE users ADD UNIQUE (email); -
INSERT IGNORE对非冲突字段的错误(如类型不匹配、NOT NULL 字段为空)仍会报错,它只“忽略”键冲突
ON DUPLICATE KEY UPDATE 怎么写才安全
这个语句看似简单,但容易踩两个坑:一是误更新无关字段,二是用错 VALUES() 引用方式。
- 更新字段必须显式列出,不要写成
ON DUPLICATE KEY UPDATE col = col—— 这会导致无意义赋值,影响mysql_affected_rows()判断 - 想把 INSERT 中的值直接用于更新,用
VALUES(col),例如:INSERT INTO users (id, name) VALUES (1, 'alice') ON DUPLICATE KEY UPDATE name = VALUES(name); - 如果只想在某些条件下更新(比如仅当新值非空),可加
IF:name = IF(VALUES(name) != '', VALUES(name), name) - 注意:
LAST_INSERT_ID()在触发更新时仍返回原插入 ID,不是被更新行的 ID
没有唯一索引时怎么避免重复插入
当无法改表结构(比如生产环境不允许加索引),又必须防重,只能靠应用层 + SQL 协同控制,但不能只靠 SELECT + INSERT 两步。
- 单纯先查后插,在并发下必然漏判 —— 两个请求同时查到“不存在”,然后都插入成功
- 必须加锁:
SELECT ... FOR UPDATE锁住范围,再插入,且整个操作需包裹在事务中 - 更稳妥的做法是用
INSERT ... SELECT ... WHERE NOT EXISTS,例如:INSERT INTO users (email, name) SELECT 'a@b.com', 'Alice' FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = 'a@b.com'); - 这个写法依赖子查询原子性,但要注意 MySQL 5.7+ 才支持
DUAL;旧版本可用(SELECT 1)替代
REPLACE INTO 的隐蔽副作用
它看起来像“有就更新,没有就插入”,但实际是“删 + 插”,这会带来三个真实风险。
- 自增 ID 会递增:即使只是替换已有记录,
id字段也会分配新值,导致 ID 不连续、主从 auto_increment 偏移 - 外键级联可能被触发:如果关联表设了
ON DELETE CASCADE,旧记录删除会连带删掉子表数据 - 未指定字段变 NULL:比如表有 5 列,
REPLACE INTO t(a,b) VALUES(1,2)会把 c/d/e 全设为 NULL,而非保留原值 - 真正需要“覆盖式更新”时,优先选
ON DUPLICATE KEY UPDATE,它不改变 ID,也不清空未指定字段










