如何使用go-pg向PostgreSQL批量插入多记录并确认是否需用事务?
go-pg 适配不定长切片的批量插入实现
基础不定长批量插入写法(适配任意数量记录)
你之前使用的零散传入结构体指针的写法,本质上和go-pg支持的切片传参逻辑完全一致,只要把零散传参换成直接传入结构体切片的引用,就可以完全适配不定长待插入列表的场景,不需要提前知道记录数量:
db := modelDB() // 待插入的书籍切片,支持任意长度,直接从业务逻辑动态填充即可 var books []*Book // 示例:模拟业务侧动态生成的待插入列表 for i := 0; i < 25; i++ { books = append(books, &Book{Title: fmt.Sprintf("dynamic book %d", i)}) } // 直接传入切片引用即可完成批量插入 res, err := db.Model(&books).Insert() if err != nil { panic(err) } // 插入完成后,自增主键、数据库默认值字段会自动回写到切片每个元素 fmt.Printf("插入完成,影响行数:%d,插入的书籍列表:%+v\n", res.RowsAffected(), books)
20条以上批量插入是否需要用事务?
对于单纯的单表批量插入场景,不需要额外显式开启事务:
- go-pg传入切片执行Insert时,底层会自动拼为单条多值SQL(
INSERT INTO books (title) VALUES (..), (..), ...) - 单条SQL在PostgreSQL层面本身是原子操作,有隐式事务保护,要么全量插入成功,要么全部回滚,没有中间状态
- 20条的规模很小,单条SQL执行的网络往返只有1次,性能已经足够,额外加事务反而增加不必要的逻辑开销
只有两种场景需要显式开启事务:
- 批量插入需要和其他多步写操作绑定为原子单元(比如插完书籍后还要更新分类计数、插入关联标签,任意一步失败都要全部回滚)
- 旧逻辑是在循环里逐单调用单条Insert(这种情况下20条记录会产生20次数据库网络往返,把循环逻辑包进事务可以把往返次数降到1次,大幅提升性能)
事务包裹的批量插入示例
如果满足上述需要事务的场景,可以使用go-pg内置的事务执行API,不需要手动写Begin/Commit/Rollback逻辑:
ctx := context.Background() db := modelDB() var books []*Book // 动态填充任意长度的待插入书籍切片 for i := 0; i < 25; i++ { books = append(books, &Book{Title: fmt.Sprintf("tx book %d", i)}) } // RunInTransaction 会自动处理事务的开启、提交、回滚逻辑 err := db.RunInTransaction(ctx, func(tx *pg.Tx) error { // 在事务内执行批量书籍插入 _, err := tx.Model(&books).Insert() if err != nil { // 返回任意错误都会自动触发事务回滚 return err } // 可以在同一事务内执行其他关联写操作 // 比如更新书籍分类的作品总计数 _, err = tx.Model((*Category)(nil)). Where("id = ?", 1). Set("book_count = book_count + ?", len(books)). Update() if err != nil { return err } // 所有操作无错误则自动提交事务 return nil }) if err != nil { panic(err) }
注意事项
- 批量插入时传入切片必须是指针类型的切片引用(即
&[]*Book{}),否则go-pg无法自动回写数据库生成的字段值 - 单批插入的数量可以根据实际业务调整,不需要受事务选择的限制
- 对于单纯的单表插入场景,优先用内置的切片传参批量插入,不需要额外手写事务逻辑
内容的提问来源于stack exchange,提问作者Kiran Kumar
相关产品推荐
相关产品推荐

