必须用pgx.CopyFrom而非db.Exec循环插入,因其直连PostgreSQL二进制COPY协议,跳过SQL解析与单行事务开销,万条数据压测常低于200ms,性能提升5–10倍。

直接用 db.Exec 循环插入 CSV 或 JSON 数据,万级条目基本会卡在连接池耗尽、WAL 写满或主从延迟爆炸上——不是代码写错了,是事务模型没对齐数据库底层机制。
PostgreSQL 必须用 pgx.CopyFrom,别碰 Exec 循环
原生 database/sql 对批量毫无优化,而 pgx.CopyFrom 直接走 PostgreSQL 的二进制 COPY 协议,跳过 SQL 解析、单行绑定和事务开销。实测万条数据常压在 200ms 内,比 Exec 快 5–10 倍。
- 必须用
*pgx.Conn(不是*sql.DB),且连接不能经sql.Open包装 - 字段顺序、类型必须与表结构严格一致:
int64不能传int,*string不能传string - 数据得提前转成
[][]interface{}:每行一个[]interface{},不能边读 CSV 边 append —— 否则内存扛不住 - 别在 for 循环里反复调
CopyFrom;按 1000–5000 行/批分组,一批一调,避免连接协商开销累积
MySQL 拼多值 INSERT 是底线,LOAD DATA LOCAL INFILE 是上限
MySQL 没 COPY 协议,database/sql 又不支持参数化的多值 INSERT,所以你得手动拼 SQL,但安全边界必须守住:
- 字符串值必须用
mysql.Escape()(来自github.com/go-sql-driver/mysql)转义,sql.EscapeString不适用 -
max_allowed_packet错误本质是服务端限制,拼 SQL 前要预估整条语句长度,超了就切批 - 若能控制 MySQL 配置,开启
local_infile=ON,并用驱动参数allowLocalInfile=true,LOAD DATA LOCAL INFILE性能远超任何拼 SQL 方案 -
stmt.Exec循环仍是逐条 round-trip,只是省了 SQL 解析,没省事务和网络开销
通用分批事务:阈值比写法更关键
当数据库不支持 COPY(如 SQLite)或你被限制只能用标准 SQL,那就老实用事务分批,但“1000 行一批”这种建议毫无意义。真正要设的是三个动态阈值:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
batchSize:从 100 起步,逐步加到 500;若字段含TEXT或索引 > 3 个,立刻回落到 200 -
context.WithTimeout:每个事务单独设超时(如 30 秒),别复用全局context,否则一个慢批拖垮全部 - 重试条件:只对
driver.ErrBadConn、网络类错误(net.OpError)、context.DeadlineExceeded重试;主键冲突、类型错、NOT NULL违反等业务错误必须记录后跳过
GORM 的 CreateInBatches 不是银弹
它确实自动封装了事务和分批逻辑,但底层仍是 Exec 循环拼多值 INSERT,性能天花板明显:
- 对 PostgreSQL,比原生
pgx.CopyFrom慢 5–10 倍 - 对 MySQL,和手拼 SQL 差距不大
- 批次大小设为 1000 时,若某批中第 999 行报错,GORM 默认整批回滚,你无法拿到该行原始数据
- 若要用 GORM,至少把
CreateInBatches封装进pgx的CopyFrom调用链里,而不是依赖它自己生成 SQL
最易被忽略的点:所有分批逻辑都默认假设数据已校验完毕。但真实场景中,CSV 字段缺失、JSON 类型错、时间格式非法这些错误,必须在进事务前拦截,否则整批回滚后连哪一行出问题都查不到。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










