最近给 rex-hugo-sidercat 这个 sidecar 子项目做数据层改造时,在 GORM 和 Bun 之间做了一次选型调研。这篇把思考过程、实测数据和写法差异整理出来,方便后续做升级时直接参考。

1. 为什么要换 ORM?

当前子项目的数据层是纯手写 SQL + database/sql,Schema 也是内联字符串。随着表变多,几个问题开始暴露:

  • 字段类型和结构体容易漂移,改一处经常漏改另一处。
  • 原生 Scan 重复代码多。
  • Schema 变更没有版本记录,线上和本地容易对不上。

所以目标很明确:引入一个轻量、可维护、迁移可控的 ORM。

2. 候选方案

主要看了三个:

方案成熟度学习曲线风格
GORM最高平缓链式对象查询,类似 ThinkPHP/Laravel
Bun中等平缓接近手写 SQL
Ent中等陡峭代码生成 + 类型安全

因为项目只有 usersordersaccessmembership 四张表,Ent 的代码生成收益不大;GORM 最成熟但“魔法”多;Bun 在 SQL 透明度和性能上有口碑,所以重点对比 GORM 和 Bun。

ORM 选型三角:性能、SQL 透明度、生态成熟度

3. 写法差异:Bun 更像 SQL

GORM 的写法是对象化链式:

var u User
db.Where("phone = ?", phone).First(&u)

db.Model(&User{}).Where("phone = ?", phone).Update("token", newToken)

Bun 的写法更接近 SQL 原句:

var u User
err := db.NewSelect().Model(&u).Where("phone = ?", phone).Scan(ctx)

_, err = db.NewUpdate().
    Model((*User)(nil)).
    Set("token = ?", newToken).
    Where("phone = ?", phone).
    Exec(ctx)

Bun 没有 FirstSave 这种高度抽象的方法,写的代码和最终 SQL 的对应关系更直接。如果团队习惯 ThinkPHP/Laravel 那种“描述我要什么”的语义化风格,GORM 更顺手;如果想保留 SQL 的控制感,Bun 更舒服。

4. 迁移管理:Bun 没有 AutoMigrate

GORM 提供 AutoMigrate,开发阶段省事:

db.AutoMigrate(&User{}, &Order{}, &Access{}, &Membership{})

但生产环境风险高——容易误删列、改类型、或者线上线下 schema 不一致。

Bun 没有 AutoMigrate,需要搭配独立迁移工具。常见选择:

工具特点推荐度
golang-migrate最主流,SQL 文件,多数据库支持⭐⭐⭐
pressly/goose支持 SQL + Go 函数混合迁移⭐⭐⭐
bun/migrate和 Bun 风格一致,但资料少⭐⭐

结论:无论选 GORM 还是 Bun,生产环境都应该用 golang-migrategoose 管理迁移,而不是依赖 AutoMigrate。

5. 性能实测

在临时脚本里用同一台机器、同样的 SQLite 驱动(modernc.org/sqlite)、同样的数据做了基准测试。

测试项:

  • 单条插入
  • 批量插入 1000 条
  • 按唯一字段查询单条
  • 批量查询 100 条
  • 单条更新

结果(每个操作的单次耗时 / 内存分配):

测试项GORMBunBun 优势
InsertOne28175 ns/op, 81 allocs14768 ns/op, 25 allocs快约 1.9 倍,分配少 69%
InsertBatch(1000)7953726 ns/op, 12020 allocs4016759 ns/op, 7187 allocs快约 2 倍
SelectOne11763 ns/op, 67 allocs10429 ns/op, 37 allocs稍快,分配少
SelectMany(100)161400 ns/op, 1257 allocs148787 ns/op, 1136 allocs稍快
UpdateOne16234 ns/op, 65 allocs6877 ns/op, 15 allocs快约 2.4 倍,分配少 77%

实测结果:Bun 在插入和更新场景优势明显,查询场景也略胜。内存分配更少,对 GC 压力更小。

6. Bun 的缺点

选型不能只看好的地方,缺点也要说清楚:

  1. 生态和资料比 GORM 少:遇到问题能搜到的中文案例不多。
  2. 没有 AutoMigrate:开发阶段需要多一步迁移管理。
  3. 写法更底层:团队如果习惯了 GORM 的“魔法”,切到 Bun 会有适应成本。
  4. 招聘/交接成本高:熟悉 Bun 的开发者在国内明显少于 GORM。
  5. 关联查询需要自己控制:Bun 没有 GORM 的 Preload 等自动关联加载机制。

7. 选型结论

对于 rex-hugo-sidercat 这种小型 sidecar 项目(4 张表、SQLite、微信支付 + 付费内容权限):

  • 如果追求绝对成熟度和招人友好,选 GORM
  • 如果追求 SQL 透明度、性能和更低的反射开销,选 Bun,同时搭配 golang-migrate 做迁移。

从这次实测和项目长期维护的角度,我倾向于 Bun + golang-migrate。理由是:表少、查询不复杂、SQL 透明带来的可维护性收益大于 GORM 的开发效率收益。