golang sql 连接超时探究
前不久的一次线上数据库故障,让我注意到了 golang/sql 连接的超时问题。
设置连接超时的两种方式
1. SetMaxLifeTime && SetMaxIdleTime
这两个方法是官方标准库提供的,使用方式如下:
db, err := sql.Open("mysql", "root:@(127.0.0.1:3306)/oliver")
if err != nil {
panic(err)
}
defer db.Close()
db.SetConnMaxLifetime(time.Second * 60) // 设置连接最大周期时间
db.SetConnMaxIdletime(time.Second * 30) // 设置连接最大空闲时间
在底层这两个方法的原理是一样的,通过方法 connectionCleaner
每秒对连接池 freeConn
中的连接进行超时判断,对已经超时的连接关闭操作。详见代码
需要注意的一点是,这里只对空闲连接检查,对于 InUse
的连接是无效的。所以像慢查询或者比较耗时的事务来说,这两个超时方法无能为力。接下来就请出第二种方法吧。
2. Context 超时设置
hhh, 感觉备受期待的方式竟然只是平平无奇路人皆知的 context
。没错,先上代码。
db, err := sql.Open("mysql", "root:@(127.0.0.1:3306)/oliver")
if err != nil {
panic(err)
}
defer db.Close()
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(time.Second * 2)
cancel()
}()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
panic(err)
}
defer tx.Rollback()
// do something
如代码所示, 通过上述方法可以实现对慢查询以及耗时事务的超时控制,避免因连接长时间占用导致数据库锁,继而引发连接堆积。
写在最后
这两种方式其实缺一不可,一般我们感觉是设置了 SetConnMaxLifetime
就万无一失,其实还是存在连接池爆掉的风险。
我认为还应该遵从以下两点:
- 不要滥用事务。事务的职责应该清晰明了。
- 设置合理的超时时间以及上面没有提到的最大连接数。