Golang+AWS Lambda使用sql.DB时MySQL连接过多问题咨询
我完全理解你遇到的困扰——Go的sql.DB本来是设计成长期存活、多goroutine共享的,但Lambda的执行模型(尤其是环境复用的warm start机制)让常规的defer db.Close()没法彻底清理连接,导致MySQL里堆了一堆Sleep状态的连接,调小wait_timeout只是权宜之计。下面是几个更根本的解决思路:
1. 复用DB句柄+优化连接池参数(最优方案)
Lambda的执行环境会被复用,每次调用都新建sql.DB反而会加剧连接泄漏。正确的做法是把DB句柄放在全局变量,只在第一次冷启动时初始化:
var db *sql.DB func init() { var err error db, err = sql.Open("mysql", "user:pass@tcp(host:3306)/db") if err != nil { log.Fatalf("Failed to init DB: %v", err) } // 关键配置:让连接池自动清理过期连接 db.SetConnMaxLifetime(5 * time.Minute) // 连接最大存活时间,到期自动关闭 db.SetMaxIdleConns(2) // 限制空闲连接池大小 db.SetMaxOpenConns(5) // 限制同时打开的连接数 } func Handler(ctx context.Context) error { // 直接复用全局DB句柄执行操作 rows, err := db.QueryContext(ctx, "SELECT * FROM your_table") if err != nil { return err } defer rows.Close() // 业务逻辑处理... return nil }
这样设置后,连接池会主动回收过期连接,就算Lambda环境被冻结,超过ConnMaxLifetime的连接也会被MySQL主动关闭,同时复用连接还能大幅降低连接创建的开销。
2. 显式强制关闭所有连接(适合无需复用的场景)
如果你的业务场景没法复用DB句柄(比如每次调用需要不同的DB配置),可以在函数结束前主动清空连接池再关闭:
func Handler(ctx context.Context) error { db, err := sql.Open("mysql", "user:pass@tcp(host:3306)/db") if err != nil { return err } defer func() { // 先把连接池大小设为0,立即关闭所有空闲连接 db.SetMaxOpenConns(0) db.SetMaxIdleConns(0) // 再关闭DB句柄,确保所有连接都被释放 if closeErr := db.Close(); closeErr != nil { log.Printf("Warning: Failed to close DB: %v", closeErr) } }() // 业务逻辑处理... return nil }
这里的核心是先通过SetMaxOpenConns(0)和SetMaxIdleConns(0)强制清空连接池,再调用db.Close(),能确保所有连接(包括正在使用的)都会被尽快关闭,而不是被动等待连接池回收。
3. 用Lambda Context管控数据库操作生命周期
Lambda的ctx会在函数执行完毕或超时后被取消,所有数据库操作都使用带Context的方法(比如QueryContext、ExecContext),这样函数结束时,正在执行的DB操作会被立即中断,连接也会被正确回收:
func Handler(ctx context.Context) error { // 初始化或复用DB句柄... // 所有DB操作都传入Lambda的ctx _, err := db.ExecContext(ctx, "INSERT INTO your_table VALUES (?)", "data") if err != nil { return err } // 业务逻辑处理... return nil }
这能避免因为函数突然结束(比如超时)导致连接长时间处于Sleep状态,确保连接被及时放回池或关闭。
为什么defer db.Close()没生效?
Lambda的执行环境在函数结束后可能被冻结(而非进程完全退出),db.Close()只会关闭当前空闲的连接,正在使用的连接会在操作完成后被放回池。如果没配置ConnMaxLifetime,这些空闲连接会一直保持Sleep状态,直到MySQL的wait_timeout到期。上面的方案从连接池管理、显式关闭、Context控制三个层面彻底解决了这个问题。
内容的提问来源于stack exchange,提问作者nguyenhoai890

