结论:gin 整合 bun 做单表操作比 gorm 更轻、更快、sql 可控性更强,但需手动处理连接池、事务、模型标签等底层细节;追求性能与 sql 透明选 bun,追求开箱即用选 gorm。

直接说结论:Gin 整合 Bun 做单表操作,比用 GORM 更轻、更快、SQL 可控性更强,但需要手动处理更多底层细节——比如连接池配置、事务显式管理、模型标签写法差异。如果你追求性能和 SQL 透明度,Bun 是更优解;如果希望开箱即用、自动迁移、软删等“魔法”多,GORM 仍更省心。
初始化 Bun 数据库连接时必须显式配置连接池
Bun 默认不启用连接池复用,db.Open 返回的 *bun.DB 实际上只是个包装器,底层 sql.DB 需要你主动调优。否则高并发下容易出现 dial tcp: too many open files 或连接超时。
- 必须设置
SetMaxOpenConns和SetMaxIdleConns,例如:db := bun.NewDB(sqlDB, dialect) db.AddQueryHook(bunquery.NewQueryLogger()) // 可选:打印 SQL sqlDB.SetMaxOpenConns(20) sqlDB.SetMaxIdleConns(10)
- MySQL 连接字符串里必须带
parseTime=true&loc=Local,否则time.Time字段解析失败,报错类似cannot scan type []uint8 into Go struct field XXX.CreatedAt - 别用
bun.NewDB(sql.Open(...), ...)一步到位——sql.Open不会立即建连,错误延迟暴露;建议先sql.Open,再db.Ping()校验
Bun 模型定义不支持隐式主键推导,必须显式标注
GORM 里 type User struct { ID uint } 自动识别 ID 为主键;Bun 不行,所有主键、自增、索引都得靠 tag 显式声明,否则 db.Insert 报 no primary key defined for model。
- 主键必须用
bun:",pk,autoincrement"(MySQL)或bun:",pk,identity"(PostgreSQL),例如:type Demo struct { bun.BaseModel `bun:"table:demos"` ID int64 `bun:"id,pk,autoincrement"` Field1 int `bun:"field1"` Field2 string `bun:"field2"` CreatedAt time.Time `bun:"created_at,nullzero"` } -
bun.BaseModel只提供空结构体,不自动注入字段;时间字段如CreatedAt必须自己定义并加bun:"created_at"标签 - 字段名与列名不一致时,不能只靠首字母大写映射(如 Go 的
CreateTime→ SQL 的create_time),必须显式写bun:"create_time"
单表 CRUD 调用方式与 GORM 差异明显,尤其查询和更新
Bun 没有 First/Save 这类封装方法,所有操作基于 db.NewSelect()/db.NewUpdate() 构建器,链式调用终止于 .Scan() 或 .Exec()。好处是 SQL 清晰可见,坏处是初学容易漏掉必要环节。
- 查单条必须用
.Where("id = ?", id).Limit(1),否则NewSelect().Model(&u).Where(...)可能返回多条却只填第一个:var demo Demo err := db.NewSelect().Model(&demo).Where("id = ?", 1).Scan(ctx) - 更新必须指定字段,
db.NewUpdate().Model(&demo).Where("id = ?", demo.ID).Exec(ctx)不会跳过零值字段(不像 GORM 的Save);若只想改部分字段,用.Column("field1", "field2") - 插入时若主键为自增,
ID字段传 0 即可;但若想获取插入后的 ID,得用.Returning("id")+Scan,而不是 GORM 的db.Create(&u).ID
Bun 的链式构建器看着灵活,但每个 .Where、.Order、.Limit 都是独立条件,拼错顺序或漏掉 .Scan 就静默失败——没有 panic 提示,只有空结果或 nil error。实际项目里建议把常用查询封装成方法,避免重复写 NewSelect().Model(...)。











