因为gorm的默认行为(如自动建表、隐式事务、零值过滤)易引发线上数据异常,而bun作为sql优先的查询构建器,不干涉schema、不重写sql、不自动提交,连接池、query构建、scan映射职责清晰,适合需精细控制sql生命周期的微服务场景。

为什么不用 GORM 而选 Bun?
因为 GORM 的默认行为(如自动创建表、隐式事务、字段零值覆盖)在微服务中容易引发线上数据异常,而 Bun 是纯粹的查询构建器 + 驱动封装,不干涉 schema、不重写 SQL、不自动 commit/rollback。它把连接池管理、Query 构建、Scan 映射三件事拆得清楚,适合需要精细控制 SQL 生命周期的场景。
常见错误现象:sql: connection pool exhausted 或 context deadline exceeded 往往不是 Bun 问题,而是没正确复用 *bun.DB 实例,或在 HTTP handler 里反复 bun.NewDB()。
- 一个服务进程只应初始化一次
*bun.DB,全局复用 - 不要在每个 handler 里 new DB;也不要把
*bun.DB当参数层层传递,用依赖注入或全局变量更稳妥 -
bun.NewDB()底层调用sql.Open(),但会额外注册 driver,重复调用会导致连接泄漏
如何配置 Bun 连接池与超时
Bun 本身不管理连接池参数,全靠底层 *sql.DB 控制。必须显式设置 SetMaxOpenConns、SetMaxIdleConns 和 SetConnMaxLifetime,否则默认值(0 / 2 / 0)极易打满数据库连接。
典型微服务推荐配置(PostgreSQL):
db := bun.NewDB(sqlDB, dialect) sqlDB.SetMaxOpenConns(20) sqlDB.SetMaxIdleConns(10) sqlDB.SetConnMaxLifetime(5 * time.Minute)
-
SetMaxOpenConns建议设为数据库单节点连接数上限的 70%~80% -
SetMaxIdleConns通常设为SetMaxOpenConns / 2,避免空闲连接长期占位 -
SetConnMaxLifetime必须小于数据库侧的tcp_keepalive_time,否则出现 stale connection - 如果用 pgx v5 驱动,记得用
bun/dialect/pgdialect,别错用pg(lib/pq)
怎么写安全的 WHERE 条件和批量 INSERT
Bun 的 Where 不做参数绑定校验,直接拼字符串会引入 SQL 注入;而 INSERT ... VALUES 批量写入时,若结构体字段含零值(如 int 为 0),Bun 默认跳过该字段 —— 这在微服务中常导致意外 NULL 或默认值覆盖。
正确做法:
- WHERE 条件一律用
Where("id = ?", id)或Where("status IN (?)", bun.In(statuses)),禁用Where("id = " + strconv.Itoa(id)) - 批量 INSERT 时,用
db.Model(&items).On("CONFLICT DO NOTHING").Insert()替代裸 SQL,避免手动拼 VALUES - 若字段必须写零值(如 status=0 表示“初始态”),在 struct tag 加
bun:",nullzero",否则 Bun 会忽略它 -
bun.In()只支持 slice,传 map 或 array 会 panic,提前if len(ids) == 0guard
事务里如何避免 context cancel 泄漏
微服务中常见模式:HTTP 请求带 ctx,进事务后用 db.WithContext(ctx).NewTx(),但忘了在 defer 里 tx.Close() 或 tx.Rollback()。一旦 ctx cancel,tx 不释放,连接就卡在 idle in transaction 状态。
安全写法只有两种:
- 用
defer tx.Close()+if err != nil { tx.Rollback() },且 Rollback 必须检查 error(可能因网络断开失败) - 更推荐用
db.RunInTx(ctx, &sql.TxOptions{}, fn),它自动 handle rollback on panic/error,并保证 Close - 注意:
RunInTx的fn接收的是*bun.Tx,不是*bun.DB,所有 query 必须通过 tx 调用 - 不要在事务里调其他 service client(如 gRPC),除非它们也接受并透传同一 ctx,否则可能提前 cancel tx
最易被忽略的是:Bun 的 Model().Select() 在事务内执行时,仍会走主连接池,除非显式调用 tx.Model() —— 这个细节不看源码根本意识不到。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











