默认情况下insert into select遇主键冲突直接报错中断事务;可用insert ignore跳过冲突行,或on duplicate key update实现空更新/字段更新,亦可前置not exists筛选避免冲突。

INSERT INTO SELECT 遇到主键冲突时会直接报错
默认情况下,INSERT INTO ... SELECT 不做任何冲突检查,只要目标表有主键或唯一约束,遇到重复值就会抛出错误,比如 MySQL 的 ERROR 1062 (23000): Duplicate entry '...' for key 'PRIMARY',PostgreSQL 则是 duplicate key value violates unique constraint。这不是“没生效”,而是事务被中断了——整条语句失败,不会插入任何行。
用 INSERT IGNORE 或 ON CONFLICT 忽略冲突行(MySQL / PostgreSQL)
不同数据库语法差异大,不能混用:
- MySQL 支持
INSERT IGNORE INTO ... SELECT:跳过导致主键/唯一冲突的行,其余正常插入;但注意它也会忽略其他错误(如数据截断),不够精准 - MySQL 8.0.19+ 更推荐
INSERT ... SELECT ... ON DUPLICATE KEY UPDATE id=id(空更新),只忽略主键冲突,不掩盖其他问题 - PostgreSQL 必须用
INSERT ... SELECT ... ON CONFLICT (id) DO NOTHING,括号里必须明确写冲突列名(如主键字段id),不能省略 - SQLite 用
INSERT OR IGNORE INTO ... SELECT
先 SELECT 再过滤,用 NOT EXISTS 或 LEFT JOIN 排除已有主键
如果需要完全可控、不依赖数据库特有语法(比如跨库迁移或 ORM 场景),就该在 SELECT 侧提前筛掉目标表已存在的主键值:
INSERT INTO users (id, name, email) SELECT s.id, s.name, s.email FROM staging_users s WHERE NOT EXISTS ( SELECT 1 FROM users u WHERE u.id = s.id );
这个写法兼容所有 SQL 标准数据库,但要注意:staging_users 如果数据量大,NOT EXISTS 可能比 LEFT JOIN ... IS NULL 慢;另外,务必确认子查询里的关联条件只匹配主键(或唯一键),否则逻辑会出错。
批量插入前检查主键是否已存在,避免隐式全表扫描
很多人直接写 WHERE id NOT IN (SELECT id FROM target),这是危险操作:如果 SELECT id FROM target 返回 NULL,整个 NOT IN 判定恒为 FALSE,结果集为空——一整批数据都插不进去,还查不出原因。
- 永远优先用
NOT EXISTS或LEFT JOIN ... WHERE t.id IS NULL - 确保目标表的主键字段有索引(通常默认有),否则
NOT EXISTS子查询可能触发全表扫描,性能骤降 - 如果源数据来自外部系统(如 CSV 导入中间表),建议先在中间表上建临时索引,再执行
INSERT ... SELECT
真正容易被忽略的是:冲突处理方式不是语法问题,而是业务语义问题——你得先想清楚,“重复时该跳过”和“重复时该更新某些字段”是两种完全不同的需求,选错方案会导致数据逻辑错误,比报错更难排查。










