Bun.QueryRow() 返回 sql.ErrNoRows 是正常行为,需显式判断 err == sql.ErrNoRows;推荐用 QueryOne() 配合 bun.ErrNoRows,语义更清晰。

为什么 Bun.QueryRow() 返回 sql.ErrNoRows 却查不到错在哪
Bun 默认不自动处理空结果,QueryRow() 遇到无匹配行就直接返回 sql.ErrNoRows,不是你 SQL 写错了,而是没显式检查这个错误。很多人直接对返回的 struct 赋值,panic 就来了。
- 必须用
err == sql.ErrNoRows显式判断,不能只写if err != nil - 如果业务上“查不到”是合法状态(比如查用户详情),就该提前分支处理,别让错误穿透到上层
- 用
QueryOne()替代QueryRow()更安全——它会把sql.ErrNoRows转成bun.ErrNoRows,语义更清晰
var user User
err := db.NewSelect().Model(&user).Where("id = ?", 123).Scan(ctx)
if errors.Is(err, bun.ErrNoRows) {
// 处理不存在
} else if err != nil {
// 其他错误
}
PostgreSQL 的 jsonb 字段在 Bun 里怎么映射才不丢数据
Bun 对 PostgreSQL 原生类型支持有限,jsonb 默认当字符串处理,一存一取就变成转义后的字符串,不是原始结构体。
- 字段类型必须声明为
json.RawMessage或自定义类型(实现driver.Valuer和sql.Scanner) - 别用
map[string]interface{}—— 它序列化时 key 顺序不定,且嵌套深了容易 panic - 如果要查询
jsonb内部字段(比如data->'user'-'name'),得用Expression,Bun 不支持链式点语法
type Post struct {
ID int `bun:"id,pk"`
Data json.RawMessage `bun:"data,type:jsonb"`
}
// 查询 jsonb 内容
db.NewSelect().ColumnExpr("data->'user'->>'name' as name").From("posts")...
bun.DB.Insert() 批量插入慢,CPU 占用高怎么办
Bun 默认每条记录生成独立 INSERT 语句,1000 行就是 1000 次 round-trip,还带参数绑定开销。PostgreSQL 支持多值 INSERT,但 Bun 不自动合并。
- 手动拼接单条
INSERT ... VALUES (...), (...), (...),用db.Exec()发送,绕过 ORM 层 - 用
db.NewInsert().Model(&slice).Exec()—— 这才是 Bun 的批量入口,但注意它底层仍可能分片(取决于pgx驱动限制和 Bun 版本) - 确保表有主键或唯一约束,否则
ON CONFLICT无法用;想忽略重复就加.On("CONFLICT DO NOTHING")
users := []User{{Name: "a"}, {Name: "b"}}
_, err := db.NewInsert().Model(&users).Exec(ctx) // 正确的批量方式
事务里嵌套 db.NewSelect() 为什么没走事务连接
Bun 的查询构造器(NewSelect/NewInsert 等)默认用的是全局 *bun.DB,不是事务对象。即使你在 tx.Run() 里调,不显式绑定,查的还是主连接。
立即学习“go语言免费学习笔记(深入)”;
- 所有事务内操作,必须用事务对象创建查询:不是
db.NewSelect(),而是tx.NewSelect() - 传参时别漏掉
ctx,Bun 的事务依赖 context 取连接,否则可能复用其他 goroutine 的连接 - 事务超时、cancel 都靠 ctx 控制,不用
context.WithTimeout()包一层,就等于没设界
最常被忽略的一点:Bun 的 tx 是一次性对象,不能缓存复用。一次事务结束,下次得重新 db.BeginTx()。










