最近给 rex-hugo-sidercat 这个 sidecar 子项目做数据层改造时,在 GORM 和 Bun 之间做了一次选型调研。这篇把思考过程、实测数据和写法差异整理出来,方便后续做升级时直接参考。
1. 为什么要换 ORM?
当前子项目的数据层是纯手写 SQL + database/sql,Schema 也是内联字符串。随着表变多,几个问题开始暴露:
- 字段类型和结构体容易漂移,改一处经常漏改另一处。
- 原生
Scan重复代码多。 - Schema 变更没有版本记录,线上和本地容易对不上。
所以目标很明确:引入一个轻量、可维护、迁移可控的 ORM。
2. 候选方案
主要看了三个:
| 方案 | 成熟度 | 学习曲线 | 风格 |
|---|---|---|---|
| GORM | 最高 | 平缓 | 链式对象查询,类似 ThinkPHP/Laravel |
| Bun | 中等 | 平缓 | 接近手写 SQL |
| Ent | 中等 | 陡峭 | 代码生成 + 类型安全 |
因为项目只有 users、orders、access、membership 四张表,Ent 的代码生成收益不大;GORM 最成熟但“魔法”多;Bun 在 SQL 透明度和性能上有口碑,所以重点对比 GORM 和 Bun。

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 没有 First、Save 这种高度抽象的方法,写的代码和最终 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-migrate 或 goose 管理迁移,而不是依赖 AutoMigrate。
5. 性能实测
在临时脚本里用同一台机器、同样的 SQLite 驱动(modernc.org/sqlite)、同样的数据做了基准测试。
测试项:
- 单条插入
- 批量插入 1000 条
- 按唯一字段查询单条
- 批量查询 100 条
- 单条更新
结果(每个操作的单次耗时 / 内存分配):
| 测试项 | GORM | Bun | Bun 优势 |
|---|---|---|---|
| InsertOne | 28175 ns/op, 81 allocs | 14768 ns/op, 25 allocs | 快约 1.9 倍,分配少 69% |
| InsertBatch(1000) | 7953726 ns/op, 12020 allocs | 4016759 ns/op, 7187 allocs | 快约 2 倍 |
| SelectOne | 11763 ns/op, 67 allocs | 10429 ns/op, 37 allocs | 稍快,分配少 |
| SelectMany(100) | 161400 ns/op, 1257 allocs | 148787 ns/op, 1136 allocs | 稍快 |
| UpdateOne | 16234 ns/op, 65 allocs | 6877 ns/op, 15 allocs | 快约 2.4 倍,分配少 77% |
实测结果:Bun 在插入和更新场景优势明显,查询场景也略胜。内存分配更少,对 GC 压力更小。
6. Bun 的缺点
选型不能只看好的地方,缺点也要说清楚:
- 生态和资料比 GORM 少:遇到问题能搜到的中文案例不多。
- 没有 AutoMigrate:开发阶段需要多一步迁移管理。
- 写法更底层:团队如果习惯了 GORM 的“魔法”,切到 Bun 会有适应成本。
- 招聘/交接成本高:熟悉 Bun 的开发者在国内明显少于 GORM。
- 关联查询需要自己控制:Bun 没有 GORM 的
Preload等自动关联加载机制。
7. 选型结论
对于 rex-hugo-sidercat 这种小型 sidecar 项目(4 张表、SQLite、微信支付 + 付费内容权限):
- 如果追求绝对成熟度和招人友好,选 GORM。
- 如果追求 SQL 透明度、性能和更低的反射开销,选 Bun,同时搭配
golang-migrate做迁移。
从这次实测和项目长期维护的角度,我倾向于 Bun + golang-migrate。理由是:表少、查询不复杂、SQL 透明带来的可维护性收益大于 GORM 的开发效率收益。
