默认会报错;insert into select 遇主键或唯一约束冲突时直接抛出 error 1062 等错误,不自动去重,需配合 insert ignore、on duplicate key update(mysql)或 on conflict(postgresql)等机制处理。

INSERT INTO SELECT 遇到重复主键或唯一约束时会直接报错
默认情况下,INSERT INTO SELECT 不做去重判断,只要目标表存在 PRIMARY KEY 或 UNIQUE 约束,遇到重复值就会抛出错误(如 MySQL 的 ERROR 1062: Duplicate entry,PostgreSQL 的 duplicate key value violates unique constraint)。它本身不提供“跳过重复”的语义,必须靠额外机制控制。
MySQL:用 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE
INSERT IGNORE 是最轻量的跳过方式——遇到重复键时整行静默丢弃,不报错也不插入;而 ON DUPLICATE KEY UPDATE 允许你指定冲突时更新哪些字段(比如只更新 updated_at)。
示例(跳过重复):
INSERT IGNORE INTO users (id, name, email) SELECT id, name, email FROM temp_users;
注意点:
-
IGNORE会抑制所有警告,包括非唯一性问题(如数据截断),调试时容易掩盖其他问题 - 必须确保
SELECT中的id字段与目标表的主键/唯一索引对齐,否则不会触发忽略逻辑 - 不支持在同一个语句中混合“插入新行 + 更新旧行”,得选一种策略
PostgreSQL:用 INSERT ... ON CONFLICT DO NOTHING 或 DO UPDATE
PostgreSQL 用 ON CONFLICT 明确指定冲突目标(通常是索引名或列名),比 MySQL 更精确。最常用的是 DO NOTHING(完全跳过)和 DO UPDATE SET ...(更新已有行)。
示例(仅跳过):
INSERT INTO users (id, name, email) SELECT id, name, email FROM temp_users ON CONFLICT (id) DO NOTHING;
关键细节:
-
ON CONFLICT (id)中的id必须是目标表上已存在的UNIQUE或PRIMARY KEY索引列,不能写不存在的列 - 如果冲突基于联合唯一索引(如
(email, tenant_id)),括号里要写全:ON CONFLICT (email, tenant_id) -
DO UPDATE中的EXCLUDED是个伪表,代表本次想插入但被拦下的那行数据,要用EXCLUDED.name这样引用
通用替代方案:先用 NOT EXISTS 或 LEFT JOIN 过滤再插入
如果数据库不支持上述语法(比如老版本 SQLite 或某些中间件限制),或者你需要更复杂的去重逻辑(比如按时间取最新一条),就得把去重逻辑写进 SELECT 子句里。
常见写法(以 MySQL/PostgreSQL 兼容为主):
INSERT INTO users (id, name, email) SELECT t.id, t.name, t.email FROM temp_users t WHERE NOT EXISTS ( SELECT 1 FROM users u WHERE u.id = t.id );
这个方法可控性强,但要注意:
- 子查询性能依赖
users(id)是否有索引,没索引时大表会很慢 - 并发插入时仍可能因检查-插入非原子性导致重复(需配合事务 + 适当的隔离级别,如
REPEATABLE READ) - 不能用于“冲突时更新字段”的场景,纯插入过滤
真正麻烦的不是语法怎么写,而是想清楚:重复时你到底要“丢弃”“覆盖”还是“部分更新”。选错策略会导致数据丢失或状态不一致,尤其是带时间戳或计数器的字段。











