结论:不设唯一约束,所有“避免重复插入”的SQL都无效;真正起作用的是表上是否存在PRIMARY KEY或UNIQUE索引,而非INSERT IGNORE等语法本身。

直接说结论:不设唯一约束,所有“避免重复插入”的 SQL 都无效。真正起作用的不是 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE 这些语法,而是表上有没有 PRIMARY KEY 或 UNIQUE 索引。
为什么 INSERT IGNORE 总是插入成功,却没跳过重复?
常见现象:执行 INSERT IGNORE INTO user (username) VALUES ('alice'),明明表里已有 username = 'alice' 的记录,但还是报错或插入了两条。
根本原因:username 字段没建唯一索引。MySQL 根本不知道“重复”指什么,自然不会触发 ignore 逻辑。
- 必须先执行类似
ALTER TABLE user ADD UNIQUE INDEX idx_username (username) -
INSERT IGNORE只对主键或唯一键冲突生效,其他错误(如字段超长、类型不符)也会被静默忽略——这是危险点 - 执行后用
SELECT ROW_COUNT()查影响行数:1表示插入,0才表示被忽略
ON DUPLICATE KEY UPDATE 为什么更新没生效?
写法看着没问题:INSERT INTO user (id, name) VALUES (1, 'bob') ON DUPLICATE KEY UPDATE name = 'bob',但旧记录的 name 没变。
问题常出在两处:
- 冲突字段没命中唯一约束:比如你用
id冲突,但表里id是自增主键,而你插入时没指定值,MySQL 自动生成新id,根本不会触发 duplicate - UPDATE 赋值写错了:别写
name = 'bob'这种固定值,应写name = VALUES(name),否则批量插入多条时所有重复项都会被设成同一个值 - 如果更新字段本身也有唯一约束(比如同时更新
email),可能引发二次冲突,语句直接失败
REPLACE INTO 导致自增 ID 跳号甚至数据丢失
执行 REPLACE INTO user (username, age) VALUES ('carol', 28) 后发现 id 从 100 变成 102,中间空了一位;更糟的是关联表外键没同步更新,数据不一致。
这是因为 REPLACE INTO 底层是「DELETE + INSERT」:
- 原记录被删,自增计数器仍递增,ID 不可复用
- 触发器会执行两次(DELETE 和 INSERT 各一次),可能误删日志或发通知
- 如果表有外键且没设
ON DELETE CASCADE,操作直接报错 - 它不区分“冲突字段”,只要任意一个唯一键匹配就全删——比预期激进得多
WHERE NOT EXISTS 性能崩盘的临界点
当数据量超过 10 万行,还用 INSERT INTO user SELECT 'dan', 'm' FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM user WHERE username = 'dan'),单条插入耗时从几毫秒升到秒级。
原因很实在:
- 每次都要跑一次子查询,无法利用唯一索引的快速查找,走的是全表扫描
- MySQL 优化器对
NOT EXISTS的执行计划常不够友好,尤其嵌套深或条件复杂时 - 并发高时容易锁表,因为子查询需要读取快照,而主 INSERT 需要加写锁
- 它唯一优势是不要求字段有唯一索引——但这恰恰说明:你本该先加索引,而不是绕开它
最易被忽略的一点:NULL 值在唯一索引里的行为。比如 UNIQUE KEY (email),插入两条 email = NULL 的记录不会冲突——MySQL 认为 NULL ≠ NULL。业务上真要允许空邮箱且去重,得用函数索引(如 UNIQUE(email) 改成 UNIQUE(COALESCE(email, '')))或应用层兜底。


















