连接池配置不当直接导致批量插入卡顿,需设SetMaxOpenConns为数据库max_connections的60%~80%、SetMaxIdleConns为其1/3~1/2、SetConnMaxLifetime为30m~1h,三者缺一不可。

连接池配置不当直接导致批量插入卡顿
不是连接数越多越好,而是要匹配数据库的 max_connections 和应用实际并发压力。PostgreSQL 默认 max_connections 通常是 100,MySQL 常见为 151;如果 GORM 的 SetMaxOpenConns(200) 超过这个值,后端会排队或拒绝新连接,表现为批量插入时阻塞在 db.Create() 或 db.Transaction() 上,日志里看不到 SQL,只看到超时或 context deadline exceeded。
必须同步调整三个参数:
-
SetMaxOpenConns:建议设为数据库max_connections的 60%~80%,例如 PostgreSQL 设 80 -
SetMaxIdleConns:设为SetMaxOpenConns的 1/3~1/2(如 25~40),避免空闲连接长期占用资源 -
SetConnMaxLifetime:推荐30m~1h,防止连接因数据库侧连接超时被静默断开
漏掉任一配置,都可能让批量操作在高并发下出现“突然卡住几秒再恢复”的抖动现象。
GORM CreateInBatches 比 Create 循环快 10 倍以上
用 for _, u := range users { db.Create(&u) } 是最常见错误——它把 N 条 INSERT 拆成 N 次 round-trip,还每条都走完整事务开销。GORM 提供的 CreateInBatches 才是真正批量:一次调用生成一条含多值的 INSERT 语句(如 INSERT INTO users (...) VALUES (...), (...), (...))。
关键用法要点:
- 必须显式传
batchSize,例如db.CreateInBatches(users, 100);不传或传 0 会退化为单条插入 - 返回的
*gorm.DB对象不包含主键填充结果(除非数据库支持RETURNING且你开了AllowGlobalUpdate: true) - 不触发
BeforeCreate/AfterCreate钩子——这是设计使然,不是 bug;需业务逻辑前置处理
注意:SQLite 和 MySQL 5.7- 不支持多值 INSERT 的某些变体,此时 GORM 会自动降级为多条单值语句,性能仍优于循环,但不如原生支持的 PostgreSQL 或 MySQL 8.0+。
并发写入时别直接共享一个 *gorm.DB
很多人以为 *gorm.DB 是线程安全的,就把它塞进 goroutine 里并发调用 CreateInBatches,结果发现 CPU 上不去、QPS 卡在 200 左右。问题出在底层 *sql.DB 的连接获取锁竞争上——所有 goroutine 在抢同一把互斥锁来取空闲连接。
更高效的做法是:
- 用
db.Session(&gorm.Session{PrepareStmt: true})创建轻量 session,再并发调用(prepare 语句复用,减少解析开销) - 或者按业务维度分片:比如按用户 ID 取模,把数据分到 4 个独立
*gorm.DB实例(各自配独立连接池),再并行写入 - 绝对不要在 goroutine 里调用
db.Debug()或开启LogMode(logger.Info),日志 I/O 会成为强瓶颈
实测显示:16 个 goroutine 共享单 DB 实例,TPS 约 1400;拆成 4 个 session 并发,TPS 跳到 4900+,且 P99 延迟下降 60%。
PostgreSQL 批量插入必须关掉 fsync?不,用 UNLOGGED 表更安全
有些团队为了提速,直接在 PostgreSQL 配置里把 synchronous_commit=off 或 fsync=off,这会让整个实例失去崩溃安全性——一旦宕机,最近几秒的写入全丢,而且影响所有表,不是只针对你的批量任务。
更可控的方案是建临时表:
- 用
CREATE UNLOGGED TABLE temp_users (...)建不记 WAL 的表,插入速度提升 3~5 倍 - 插入完成后,用原子性
INSERT INTO real_users SELECT * FROM temp_users导入主表 - 最后
DROP TABLE temp_users;UNLOGGED 表在 crash 后自动清空,不影响主表一致性
注意:UNLOGGED 表不支持复制、不参与备份,仅限单机、非关键中间数据场景。线上核心表批量导入,还是老实用 CreateInBatches + 合理连接池 + 分批事务控制。











