PostgreSQL批量插入冲突时必须用原生SQL写ON CONFLICT子句,GORM的CreateInBatches不支持该语法;正确写法为INSERT INTO ... VALUES (...) ON CONFLICT (col) DO NOTHING/DO UPDATE SET ...,需配合pgx占位符与RETURNING获取结果。

批量插入时 ON CONFLICT 怎么写(PostgreSQL)
PostgreSQL 的 ON CONFLICT 是唯一键冲突时最可控的方式,但 GORM 原生不支持直接拼这个子句到 CreateInBatches 里。必须用原生 SQL 或 Session + Exec 配合 RETURNING 手动处理。
常见错误是以为 CreateInBatches 能自动跳过重复记录——它不能,遇到唯一约束会直接报 ERROR: duplicate key value violates unique constraint 并中断整个批次。
实操建议:
- 用
db.Exec执行带ON CONFLICT DO NOTHING或DO UPDATE SET ...的INSERT INTO ... VALUES ... ON CONFLICT语句 - VALUES 部分用
pgx风格占位符($1, $2),避免字符串拼接注入 - 如果需返回插入/更新后的 ID 或状态,加上
RETURNING id, xmax,再用Rows.Scan解析 - GORM v1.25+ 提供了
Clause(clause.OnConflict{...}),但仅对单条Create有效,CreateInBatches不识别该 Clause
Save 和 FirstOrCreate 能否替代批量插入?
不能当批量方案用。它们本质是「查 + 插/更新」的两步操作,在高并发下有竞态风险,且性能极差:每条记录都触发一次 SELECT + INSERT/UPDATE。
立即学习“go语言免费学习笔记(深入)”;
典型误用场景:
- 循环调用
db.FirstOrCreate(&user, User{Email: "x@example.com"})插入 1000 条 —— 产生 2000 次查询,还可能因并发导致重复插入失败 - 用
Save期望自动忽略冲突 —— 实际上它会尝试 UPDATE,若记录不存在则报错record not found - 想靠事务包裹多个
FirstOrCreate来“原子化” —— 事务无法消除 SELECT 和 INSERT 之间的窗口期
MySQL 的 ON DUPLICATE KEY UPDATE 怎么适配
MySQL 没有 ON CONFLICT,对应的是 ON DUPLICATE KEY UPDATE。GORM 对它的支持比 PostgreSQL 更弱:v2.x 完全不解析该子句,必须走原生 SQL。
注意点:
-
INSERT IGNORE可以跳过冲突,但不会返回受影响行数,也无法区分是“插入成功”还是“被忽略” -
ON DUPLICATE KEY UPDATE要求明确指定更新字段,例如ON DUPLICATE KEY UPDATE updated_at = NOW(),不能写UPDATE *或空UPDATE - GORM 的
Session(&gorm.Session{DryRun: true})查不到真实 SQL,别指望靠它调试ON DUPLICATE逻辑 - 如果主键是自增 ID,
ON DUPLICATE KEY UPDATE不会改变 ID 值,但LAST_INSERT_ID()在冲突时返回 0 —— 这个细节常被忽略,导致后续逻辑误判
硬编码 INSERT ... SELECT ... WHERE NOT EXISTS 安全吗?
安全,但要自己保证字段顺序、类型匹配和 NULL 处理。这是跨数据库兼容性最好的纯 SQL 方案,尤其适合 MySQL / SQLite / PostgreSQL 通用场景。
关键限制:
- VALUES 必须转成子查询,例如
SELECT 'a', 123 UNION ALL SELECT 'b', 456,不能直接写VALUES (...), (...)(SQLite 不支持) -
WHERE NOT EXISTS的子查询必须包含全部唯一键字段,漏一个就会重复插入 - 字段名必须显式写出,不能依赖
*;否则 GORM 自动加的created_at等字段可能引发类型不匹配 - 大批次(>1000 行)时,UNION ALL 过多会导致 SQL 长度超限,得拆成多个 500 行的批次执行
真正难的不是语法,是确认哪些字段构成业务唯一性、是否允许部分字段为空、以及冲突后该记录算“已存在”还是“需覆盖”。这些逻辑一旦写错,数据就不可逆地乱了。


















