serverless 环境下不能全局复用 gorm.db 实例,因其底层 sql.db 连接池在容器复用时可能已失效,导致 driver: bad connection 或 i/o timeout;且若不显式管理生命周期,易引发连接泄漏、too many connections 等问题。

Serverless 环境下不能复用全局 *gorm.DB 实例,必须按请求生命周期初始化并显式释放连接池资源。
为什么 Serverless 里 *gorm.DB 不能全局复用
函数实例冷启动时可能复用旧容器,但上一次调用遗留的 *gorm.DB 连接池可能已失效(数据库主动断连、连接超时、网络中断),直接复用会导致 driver: bad connection 或 i/o timeout 错误。更关键的是:Serverless 平台(如 AWS Lambda、阿里云 FC)会在函数空闲后回收容器,而 *gorm.DB 底层的 *sql.DB 持有未关闭的连接,可能引发连接泄漏或端口耗尽。
常见错误现象:
- 首次调用正常,后续调用频繁报
context deadline exceeded - 并发压测时出现大量
too many connections(数据库侧) - 函数执行完不退出,日志显示连接池持续活跃
每次请求都新建 *gorm.DB?不,要复用但要可控
完全每次请求都调用 gorm.Open() 开销太大(DNS 解析、TCP 握手、TLS 协商、认证)。正确做法是:在函数 handler 入口复用一个“轻量级”连接池实例,但限制其生命周期与当前请求一致。
实操建议:
- 使用
sync.Once在冷启动时初始化一次底层*sql.DB,但不直接暴露*gorm.DB全局变量 - 每次请求调用时,基于该
*sql.DB构造新的*gorm.DB(传入&gorm.Config{SkipDefaultTransaction: true}等轻量配置) - 务必在 handler 返回前调用
db.Close()—— 注意:这不是关闭底层连接池,而是释放该次请求关联的 GORM 会话状态;底层*sql.DB仍存活供下次复用 - 设置
sqlDB.SetConnMaxLifetime(5 * time.Minute),避免连接在长生命周期容器中僵死
SetMaxOpenConns 和 SetMaxIdleConns 怎么设才合理
Serverless 的并发模型是“每个请求一个 goroutine + 可能的并行实例”,不是传统服务的固定 Pod。连接数配置必须匹配平台最大并发数,而非 CPU 核心数。
参数选择逻辑:
-
SetMaxOpenConns建议设为平台单实例最大并发数(如 AWS Lambda 是 1000,阿里云 FC 默认 100),再乘以预留的 buffer(+20%) -
SetMaxIdleConns设为SetMaxOpenConns / 2即可,过高反而增加维护开销 - 绝对不要设
SetMaxOpenConns = 0(即无限制),这在 Serverless 下极易触发数据库连接上限熔断 - 若使用连接池代理(如 ProxySQL、MySQL Router),可适当降低该值,把连接复用交给中间件
软删除、钩子、预编译这些功能还该开吗
在 Serverless 场景下,每毫秒都影响计费和冷启延迟,要精简非必要开销:
-
PrepareStmt: true仍建议开启 —— 预编译语句缓存对重复查询(如 API 查询用户)收益明显,且只在首次调用时有编译成本 -
SkipDefaultTransaction: true必须开启 —— Serverless 函数默认无事务上下文,开启默认事务只会徒增开销 - 禁用所有全局钩子(
AfterFind、BeforeCreate等),改用显式方法封装;钩子在短生命周期中初始化/注册成本占比过高 - 软删除字段(
deleted_at)保留,但避免在高频查询中无条件加Unscoped()—— 它会绕过 GORM 自动注入的 WHERE 条件,容易误查
最易被忽略的一点:Serverless 函数退出时,*sql.DB 不会自动 Close,必须在进程退出前显式调用(例如在 main() 结束前或使用 runtime.RegisterExitHandler),否则连接可能滞留数分钟,拖垮数据库连接池。











