gorm.Open卡住实为未执行,因http.ListenAndServe阻塞导致initDB()永不调用;须先初始化数据库再启动HTTP服务,严禁将阻塞调用置于初始化之前。

gorm.Open 卡住不返回,其实是根本没执行
这不是数据库连不上,而是 gorm.Open 根本没被调用。常见写法是把 http.ListenAndServe 放在 initDB() 前面,而 ListenAndServe 是同步阻塞函数——它一运行,后面所有代码(包括 initDB)就永远卡住。
必须严格按依赖顺序初始化:
- 先调用
initDB(),确保gorm.Open执行完成、db全局变量已赋值 - 再启动 HTTP 服务:
http.ListenAndServe - 不要用
go http.ListenAndServe()绕过阻塞:会导致 handler 访问未初始化的db,引发 panic 或 nil pointer dereference
区分连接失败和其他错误,不能只看 result.Error != nil
GORM 把底层驱动错误封装成统一的 result.Error,但业务错误(如记录不存在)、约束冲突(如唯一键重复)、连接中断(如网络断开)都混在一起。直接判空或用 errors.Is(err, gorm.ErrRecordNotFound) 只能识别部分情况,对连接类异常完全无感。
正确做法是类型断言获取原始驱动错误:
- PostgreSQL:断言为
*pq.Error,检查pqErr.Code[0:2] == "08"(标准 SQLSTATE 连接类错误前缀) - MySQL:断言为
*mysql.MySQLError,检查err.Number是否属于连接超时、拒绝连接等码(如 1040、1045、2002、2013) - 无论哪种驱动,都应在查询后立即做断言,而不是等到事务提交或批量操作结束才处理
回调中触发异步逻辑,别忘了事务上下文还在
GORM 的 after_create、after_update 等回调确实在 DB 操作完成后触发,但此时事务可能尚未提交(尤其在手动事务中)。如果直接在回调里起 goroutine 发消息、调第三方 API 或写日志文件,会出现数据可见性问题:下游服务查不到刚写入的数据。
安全做法是:
- 用
db.Statement.Context获取当前事务上下文,并传给异步函数 - 或改用
CommitOrRollbackTransaction回调,在事务真正结束后再触发 - 避免在回调里直接操作
db实例——它可能正处在事务中,新查询会复用同一连接,导致锁等待或死锁
JSON 字段报错 “invalid memory address”,大概率是 Scan 没处理 nil
自定义 JSON 类型(比如 type JSON []byte)若没正确实现 Scan 方法,当数据库字段为 NULL 时,Scan(nil) 会传入 nil 值,而未判空的 *j = append((*j)[0:0], s) 就会 panic。
必须显式处理 value == nil 分支:
if value == nil { *j = nil; return nil }- 用
[]byte而非string实现,避免 UTF-8 编码转换开销和潜在的截断 - 如果用官方
datatypes.JSON,也要注意它内部仍依赖 driver 的Scan行为,某些旧版 MySQL 驱动对 JSON NULL 处理不一致,建议升级到go-sql-driver/mysql@v1.7.1+


















