beego orm批量插入必须用insertmulti而非循环insert,因后者每次触发独立事务和网络往返导致性能断崖式下降;insertmulti底层为单条insert语句,需指定每批行数(50–200)和类型一致的切片,长度不得小于批次值。

Beego ORM批量插入必须用InsertMulti,不能用循环Insert
1000 条数据用 Insert 循环调用 1000 次,性能会断崖式下跌,不是慢一点,是慢一个数量级。根本原因是每次 Insert 都触发一次独立事务、一次连接获取、一次 SQL 准备与执行,网络往返和锁竞争全拉满。
正确做法是用 InsertMulti,它底层走的是单条 INSERT INTO ... VALUES (...), (...), (...) 语句:
-
InsertMulti第一个参数是每批插入的行数(建议 50–200,太大易触发 MySQL packet limit) - 第二个参数是
[]*models.User或[]models.User切片(注意类型要一致,不能混指针和值) - 切片长度必须 ≥ 第一个参数,否则 panic;若不足,需手动分批
- 返回值是插入总行数,不是主键列表 —— 如需主键,得用
QuerySeter+Insert后查,或改用原生 SQL
Update 不带字段名默认更新全部非空字段,极易误覆盖
写 o.Update(&user) 看似简洁,但 Beego ORM 默认把 struct 中所有非零值字段都塞进 SET 子句 —— 比如 user.Password 是空字符串、user.Avatar 是 nil,它们不会被更新;但 user.Status = 0、user.Version = 0 这类零值字段会被当成“要设为 0”而强制更新,破坏业务逻辑。
安全做法永远显式指定字段:
- 只更新需要改的字段:
o.Update(&user, "status", "updated_at") - 字段名用小写蛇形(
updated_at),和模型 tag 中的column值一致,不是 struct 字段名 - 时间字段务必手动赋值:
user.UpdatedAt = time.Now(),ORM 不自动填充 - 避免用
Update更新主键(id),MySQL 会报错或静默失败
批量更新没有内置 API,得靠 QuerySeter + UpdateBatch
Beego ORM 没有类似 InsertMulti 的 UpdateMulti,想批量改多行(比如“把用户表里 status=1 的所有记录改为 2”),不能靠循环 Update,必须用 QuerySeter 的链式操作:
- 先构造条件:
qs := o.QueryTable(&models.User{}).Filter("status", 1) - 再调用
qs.Update(orm.Params{"status": 2, "updated_at": time.Now()}) -
Update在QuerySeter上调用时,才是真正的批量 SQL(UPDATE user SET ... WHERE status = 1) - 注意:
orm.Params的 key 是数据库列名(小写蛇形),value 是目标值,不支持表达式(如status + 1) - 返回值是影响行数,不是错误 —— 错误需检查
err,别只看num
事务中混合批量操作容易丢数据,InsertMulti 不参与事务自动回滚
很多人在事务里写:
tx, _ := o.Begin()
tx.InsertMulti(100, users)
tx.Update(&someUser)
if err != nil {
tx.Rollback()
}
问题在于:如果 InsertMulti 成功但 Update 失败,Rollback() 只能回滚 Update,而 InsertMulti 已提交 —— 数据库层面已落盘,事务控制失效。
根本原因:InsertMulti 底层绕过了 ORM 的事务封装,直接用了 sql.DB.Exec。解决办法只有两个:
- 全用
QuerySeter操作(Insert单条 + 手动循环,配合事务)—— 性能差但可控 - 改用原生 SQL +
tx.Raw().Exec(),自己拼INSERT ... VALUES (),(),(),确保全程在事务内 - 或者接受「批量插入不进事务」的事实,把
InsertMulti提到事务外,仅对强一致性要求高的部分用事务包裹
这点特别容易被忽略:文档没写,示例也不提,但生产环境一出错就是脏数据。











