不能在init中初始化数据库或redis连接池,因其同步阻塞i/o会导致程序卡死或panic;应移至main()开头或首个handler中,用sync.once.do配合显式错误处理安全初始化。

别在 init 里初始化数据库或 Redis 连接池——它根本活不到“断开”的那一刻,大概率直接卡死或 panic。
为什么 init 中调用 db.Ping() 或 client.Ping() 会卡住
Go 的 init 函数是单线程、无调度、同步阻塞执行的。任何网络 I/O(比如 DNS 解析、TCP 握手、TLS 协商)都会让整个程序停在那里,且 context.WithTimeout 完全无效:运行时此时禁止新 goroutine,ctx.Done() 永远不会被 select 到。
-
sql.Open()本身不拨号,但紧跟着的db.Ping()会强制建连;同理redis.NewClient().Ping(ctx)也会触发同步阻塞 - 网络不可达、防火墙拦截、DNS 延迟超 5 秒?程序就卡 5 秒以上,且无日志输出(
log包可能都还没初始化) - 某些封装库内部用了
sync.WaitGroup或 channel 等待,直接触发fatal error: all goroutines are asleep - deadlock!
globalDB 变量仍是 nil 却不报错的真实原因
典型遮蔽:你在 a.go 的 init 或 main 里写了 db, err := sql.Open(...),这行用 := 声明的是**局部变量 db**,而包级 var db *sql.DB 根本没被赋值,始终为 nil。
- 后续
b.go直接调db.Query()→panic: nil pointer dereference - 本地测试可能“侥幸”通过(只跑单个文件),CI 或线上环境必崩
- 编译不报错,运行时才暴露,且堆栈指向使用点而非初始化点,排查成本高
怎样安全初始化连接池(避开 init 的三个硬性条件)
只要违反任意一条,就必须移出 init:
- 涉及网络 I/O(
Ping、Connect、Auth)→ 改到main()开头或首个 handler 入口 - 依赖其他包状态(如配置未加载、log 未设置)→ 改用显式函数,按需顺序调用
- 需要错误恢复或日志记录 →
init中panic无法捕获,必须交由应用层处理
推荐做法:var UserDB *gorm.DB(初始 nil),提供 func SetupUserDB() error,内部用 sync.Once.Do 包裹 gorm.Open 和 PingContext,并在 main() 中显式调用。注意:别在 init 里调 sync.Once.Do——它安全,但时机仍错。
DSN 与驱动名不匹配导致的静默卡死
现象是启动后卡几秒,然后报 invalid connection 或直接 panic,但无具体错误堆栈。大概率是 DSN 和驱动注册对不上:
-
import _ "github.com/go-sql-driver/mysql",但 DSN 写成postgres://→ 应该用pgx驱动 -
sql.Open("postgres", dsn)报错?实际应为sql.Open("pgx", dsn) - GORM 中用
mysql.New()却配了 PostgreSQL 的 DSN,不会编译失败,但第一次Create就失败
验证方法:在 SetupUserDB 里加一行 log.Printf("connecting to %s with driver %s", dsn, driverName),确保两者对得上。
最易被忽略的是:即使你把 Open 移出了 init,默认池参数(如 MaxOpenConns=0 或 10)仍会让连接池在首次查询时同步建满空闲连接——这个“首次”,往往就是第一个 handler 被打进来的时候,而不是你认为的“可控时机”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











