必须调用 db.ping() 验证连接,合理设置 setmaxopenconns(如30)、setmaxidleconns 与 setconnmaxlifetime(如5m),scan 时用小写下划线 db tag,boltdb 读用 view、写用 update。

db.Open() 之后不 Ping(),连接可能根本没建立
很多人以为 sql.Open() 就连上了数据库,其实它只初始化连接池,不校验连通性。真实错误往往卡在第一次 db.Query() 或 db.Exec() 时才暴露,而且堆栈里还藏得深。
必须手动调一次 db.Ping(),并在启动阶段就做——否则服务起来后突然报 dial tcp: i/o timeout 或 connection refused,排查成本翻倍。
- 放在
NewDB()函数末尾,失败直接 panic 或返回 error - 如果用 Docker Compose,
Ping()要配合重试逻辑(比如最多 3 次,每次间隔 2 秒),避免因依赖服务启动慢而误判 - 别在 HTTP handler 里首次请求才 Ping:那不是“连接池健康检查”,是“甩锅给用户”
SetMaxOpenConns 不设上限,PostgreSQL 很快报 too many clients
PostgreSQL 默认 max_connections = 100,而 Go 的 sql.DB 默认 SetMaxOpenConns(0)(无限制)。两者一碰,高并发下必然触发数据库拒绝新连接,错误信息是 pq: sorry, too many clients already。
这不是 Go 代码写错了,是连接池和数据库配置没对齐。
-
SetMaxOpenConns建议设为 20–50,具体看 DB 规格和业务 QPS;2C4G 的 PostgreSQL 实例,30 是较稳的起点 -
SetMaxIdleConns设为跟MaxOpenConns相同值,避免空闲连接被频繁销毁重建 -
SetConnMaxLifetime必须设(比如 5m),不然长连接可能因网络中间件(如 ALB、iptables)静默断开,后续请求直接卡住
Scan 到 struct 时字段名大小写不匹配,值全为零值
Go 的 database/sql 默认按 struct 字段名(非 tag)匹配列名,且严格区分大小写。SQL 返回 user_id,而 struct 字段叫 UserID 或 userid,都会导致 Scan 失败但不报错——所有字段变成零值。
最稳妥的做法是统一用小写下划线命名,并显式加 db tag:
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
}
- 别依赖
rows.Columns()动态推导字段顺序:列序可能随 SQL 重构改变 - 用
sqlx.StructScan()或gorm.Model()可省 tag,但引入了额外依赖;纯database/sql就老老实实写 tag - 如果 SQL 用别名(如
SELECT u.id AS user_id),tag 里也得写user_id,而不是原始字段名
BoltDB 的 Update 里混用 Get 和 Put,读操作会阻塞其他写入
bolt.DB.Update() 拿的是写锁,哪怕你只调 tx.Bucket("users").Get([]byte("123")),也会阻塞后续所有 Update 调用。高频读场景下,整个 DB 就卡死了。
只读必须用 db.View(),它拿读锁,允许多个并发读,不影响写。
- 查完立刻插入这种“读-写耦合”逻辑,只能放
Update里,但要确保整个事务尽可能短(比如不做 HTTP 请求、不调外部 API) -
View内部调Put会 panic:tx is not writable,错误信息很明确,但容易忽略 - 批量写入别拆成多个
Update:100 条数据用 1 次Update+ 100 次Put,比 100 次独立Update快 5–10 倍
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











