insert ignore 没跳过重复数据的根本原因是表缺少唯一约束;它仅响应 duplicate entry 错误,依赖 unique 或 primary key 索引触发,且 null 在唯一索引中不视为重复。

INSERT IGNORE 为什么没跳过重复数据?
根本原因是表上没建唯一约束——INSERT IGNORE 不比对字段值,只响应数据库报出的 Duplicate entry 错误。没索引,它就当普通 INSERT 用,连警告都不会有。
必须满足两个条件才生效:
-
UNIQUE索引或PRIMARY KEY已存在,且覆盖你判断“重复”的字段(比如按email去重,就得ALTER TABLE users ADD UNIQUE (email)) - 插入语句中涉及的字段值,恰好触发了该索引的冲突(注意:
NULL在唯一索引中可重复,所以含NULL的行不会被跳过)
常见坑:建了普通 INDEX 而非 UNIQUE,或者复合索引漏掉某个关键字段(如业务要求 (user_id, category) 不重复,但只给 user_id 加了唯一索引)。
ON DUPLICATE KEY UPDATE 和 INSERT IGNORE 选哪个?
看你要不要“保留旧数据”:
- 只要静默跳过、不改原记录、避免自增 ID 变动、不触发
ON DELETE CASCADE或触发器 → 用INSERT IGNORE - 想在重复时更新部分字段(比如更新
updated_at或计数器),或需要通过ROW_COUNT()明确区分“新增”和“已存在” → 用ON DUPLICATE KEY UPDATE
别混淆:INSERT IGNORE 不会执行 ON DUPLICATE KEY UPDATE 子句;反过来,ON DUPLICATE KEY UPDATE 也不支持“完全跳过”,它总会执行一次更新(哪怕写成 col = col)。
没唯一索引时,还能防重复插入吗?
能,但得用子查询方案:INSERT INTO ... SELECT ... WHERE NOT EXISTS。它不依赖索引约束,靠运行时检查,适合临时场景或无法改表结构的情况。
写法要点:
- 子查询必须关联外层值,例如:
WHERE NOT EXISTS (SELECT 1 FROM t WHERE t.email = 'a@b.com'),不能写成NOT IN (SELECT email FROM t)(NULL会导致逻辑失效) - 判断字段必须有索引,否则每次插入都全表扫描,高并发下直接拖垮性能
- MySQL 中不需要额外括号;PostgreSQL 则强制要求
SELECT部分加括号
事务中它会自动加共享锁,比应用层先查后插更安全,但性能开销明显高于 INSERT IGNORE。
批量插入时,重复行会影响其他行吗?
不会。INSERT IGNORE 和 ON DUPLICATE KEY UPDATE 的“跳过”或“更新”都是按行粒度处理的。例如:
INSERT IGNORE INTO users (id, email) VALUES (1,'a@b.com'), (2,'b@b.com');
若第一行因 email 冲突被忽略,第二行仍会尝试插入,不受影响。
但要注意:mysql_affected_rows() 或 PDOStatement::rowCount() 返回的是本次语句实际变更的行数(0 表示全跳过,1 表示插入/更新了一行,2 表示两行都成功),无法告诉你哪一行被跳了——这是最容易被忽略的盲点。










