Golang中MySQL连接数过多问题如何解决
Golang中MySQL连接数过多问题如何解决
最近,我的Go应用程序遇到了MySQL数据库连接过多的问题。
max_connections 设置为 9000。
open files 设置为 102400。
以下是我用于MySQL数据库连接的代码。
由于我为每个API调用都使用相同的连接,所以没有在任何地方关闭数据库连接。
var DB *sql.DB = nil
func SetDbConnection() *sql.DB {
// Establish rules for a connection with the database
if DB == nil {
info := config.GetMysqlConfig("") // reading from yaml file
host := info["host"]
username := info["username"]
password := info["password"]
dbname := info["dbname"]
DB, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=10s", username, password, host, dbname))
if err != nil {
fmt.Println(err)
log.Println(err)
os.Exit(2)
}
DB.SetMaxIdleConns(300)
fmt.Println("Mysql Connection Done")
}
return DB
}
以下是我用于从MySQL获取数据的函数。
func GetRow(query string, params []interface{}) map[string]string {
var err error
var stmt *sql.Stmt
stmt, err = DB.Prepare(query)
if err != nil {
panic(err)
}
singleRow := true
rows, err2 := stmt.Query(params...)
stmt.Close()
if err2 != nil {
panic(err2)
}
val := make(map[string]string)
index := 0
results := map[int]map[string]string{}
cols, _ := rows.Columns()
str := ""
for rows.Next() {
columns := make([]interface{}, len(cols))
columnPointers := make([]interface{}, len(cols))
for i := range columns {
columnPointers[i] = &columns[i]
}
if err := rows.Scan(columnPointers...); err != nil {
panic(err)
}
results[index] = map[string]string{}
for i, colName := range cols {
val := columnPointers[i].(*interface{})
val2 := *val
switch val3 := val2.(type) {
case int, int8, int16, int32, int64, float32, float64:
str = fmt.Sprintf("%v", val3)
case string:
str = fmt.Sprintf("%s", val3)
case nil:
str = ""
default:
str = fmt.Sprintf("%s", val3)
}
results[index][colName] = str
}
if singleRow == true {
break
}
index++
}
rows.Close()
if val, ok := results[0]; ok {
return val
}
return val
}
func GetRows(query string, params []interface{}) map[int]map[string]string {
var err error
var stmt *sql.Stmt
stmt, err = DB.Prepare(query)
if err != nil {
panic(err)
}
rows, err2 := stmt.Query(params...)
stmt.Close()
if err2 != nil {
panic(err2)
}
index := 0
results := map[int]map[string]string{}
cols, _ := rows.Columns()
str := ""
for rows.Next() {
columns := make([]interface{}, len(cols))
columnPointers := make([]interface{}, len(cols))
for i := range columns {
columnPointers[i] = &columns[i]
}
if err := rows.Scan(columnPointers...); err != nil {
panic(err)
}
results[index] = map[string]string{}
for i, colName := range cols {
val := columnPointers[i].(*interface{})
val2 := *val
switch val3 := val2.(type) {
case int, int8, int16, int32, int64, float32, float64:
str = fmt.Sprintf("%v", val3)
case string:
str = fmt.Sprintf("%s", val3)
case nil:
str = ""
default:
str = fmt.Sprintf("%s", val3)
}
results[index][colName] = str
}
index++
}
rows.Close()
return results
}
func GetVar(query string, params []interface{}) string {
var err error
var stmt *sql.Stmt
stmt, err = DB.Prepare(query)
if err != nil {
panic(err)
}
singleRow := true
rows, err2 := stmt.Query(params...)
stmt.Close()
if err2 != nil {
panic(err)
}
index := 0
results := ""
cols, _ := rows.Columns()
str := ""
for rows.Next() {
columns := make([]interface{}, len(cols))
columnPointers := make([]interface{}, len(cols))
for i := range columns {
columnPointers[i] = &columns[i]
}
if err := rows.Scan(columnPointers...); err != nil {
panic(err)
}
for i := range cols {
val2 := *columnPointers[i].(*interface{})
switch val3 := val2.(type) {
case int, int8, int16, int32, int64, float32, float64:
str = fmt.Sprintf("%v", val3)
case string:
str = fmt.Sprintf("%s", val3)
case nil:
str = ""
default:
str = fmt.Sprintf("%s", val3)
}
results = str
}
if singleRow {
break
}
index++
}
rows.Close()
return results
}
你能建议一下可能导致这些错误的原因吗?
我的应用程序每秒有50-60个API调用,平均响应/处理时间约为2秒。
每个API调用都会调用 SetDbConnection(),但我检查了连接是否存在,然后才连接到数据库。
请给予建议。
编辑1:我正在使用 github.com/go-sql-driver/mysql
更多关于Golang中MySQL连接数过多问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html
感谢您的回复。我理解了关于 SetConnMaxLifetime() 的内容。还有其他可能导致连接泄漏的原因吗?
更多关于Golang中MySQL连接数过多问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你收到什么错误信息,以及错误出现在代码的哪一行?
是否存在其他可能导致连接泄漏的因素?
连接是通道,而会话是一种状态。一个连接可能包含多个会话。如果是连接问题,你可以通过某种方式关闭连接,以避免达到连接限制。
你收到了什么错误信息,以及在代码的哪一行?
这意味着,如果你想获得帮助,就请准确且完整地回答所提出的问题。
始终发布确切的消息文本,为了准确性,请复制并粘贴。 你对自己认为它说了什么的解读,从来都不够准确到足以回答问题。
Go 拥有非常棒的错误信息,它会确切地告诉你代码的哪一行出了问题。如果你真的想快速获得帮助,请完整地回答别人的后续问题。
你应该始终使用连接池实现,并且在用完连接后立即关闭它们。让那些经过充分调试和测试的连接池为你处理连接问题。
DB, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=10s", username, password, host, dbname))
if err != nil {
os.Exit(2)
}
defer DB.close()
// 在这里进行处理
/* 如果你需要做的事情超出了单个函数能轻松容纳的范围,那么可以使用一个 goroutine 和一个 waitGroup 来处理查询及其结果。返回连接、结果集等几乎总是一个非常糟糕的主意。 */
如果你需要对查询进行处理,请在当前作用域结束之前,在一个围绕 DB 的闭包中完成。
就像 Java 一样,让连接泄漏到创建它们的方法/函数之外,无异于自找麻烦。任何持有该连接的东西(如 ResultSet)也是如此。
学会如何阅读理解并准确回答问题,比了解某些“月度语言”的晦涩语法更重要。
你的代码存在几个关键问题导致连接数过多:
主要问题分析:
- 没有正确关闭数据库资源 - 虽然你调用了
stmt.Close()和rows.Close(),但在错误处理路径中这些资源没有被关闭 - 连接池配置不当 - 只设置了
SetMaxIdleConns,但没有设置SetMaxOpenConns - 每次查询都创建新的 prepared statement - 没有复用
修复方案:
1. 改进连接池配置
func SetDbConnection() *sql.DB {
if DB == nil {
info := config.GetMysqlConfig("")
host := info["host"]
username := info["password"]
password := info["password"]
dbname := info["dbname"]
DB, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=10s&parseTime=true",
username, password, host, dbname))
if err != nil {
log.Fatal(err)
}
// 关键配置
DB.SetMaxOpenConns(100) // 最大打开连接数
DB.SetMaxIdleConns(50) // 最大空闲连接数
DB.SetConnMaxLifetime(time.Hour) // 连接最大生命周期
DB.SetConnMaxIdleTime(30 * time.Minute) // 连接最大空闲时间
// 测试连接
if err := DB.Ping(); err != nil {
log.Fatal(err)
}
fmt.Println("Mysql Connection Done")
}
return DB
}
2. 使用 context 和 defer 确保资源释放
func GetRow(ctx context.Context, query string, params []interface{}) (map[string]string, error) {
rows, err := DB.QueryContext(ctx, query, params...)
if err != nil {
return nil, err
}
defer rows.Close()
if !rows.Next() {
return nil, sql.ErrNoRows
}
cols, err := rows.Columns()
if err != nil {
return nil, err
}
values := make([]interface{}, len(cols))
valuePtrs := make([]interface{}, len(cols))
for i := range values {
valuePtrs[i] = &values[i]
}
if err := rows.Scan(valuePtrs...); err != nil {
return nil, err
}
result := make(map[string]string)
for i, col := range cols {
if values[i] == nil {
result[col] = ""
continue
}
switch v := values[i].(type) {
case []byte:
result[col] = string(v)
default:
result[col] = fmt.Sprintf("%v", v)
}
}
return result, nil
}
3. 使用 prepared statement 缓存
var stmtCache = sync.Map{}
func GetRowWithCachedStmt(ctx context.Context, query string, params []interface{}) (map[string]string, error) {
// 从缓存获取或创建 prepared statement
stmt, ok := stmtCache.Load(query)
if !ok {
preparedStmt, err := DB.PrepareContext(ctx, query)
if err != nil {
return nil, err
}
stmtCache.Store(query, preparedStmt)
stmt = preparedStmt
}
rows, err := stmt.(*sql.Stmt).QueryContext(ctx, params...)
if err != nil {
return nil, err
}
defer rows.Close()
// ... 处理结果
}
4. 简化查询函数
func QueryRow(ctx context.Context, query string, args ...interface{}) (*sql.Row, error) {
return DB.QueryRowContext(ctx, query, args...), nil
}
func QueryRows(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
return DB.QueryContext(ctx, query, args...)
}
5. 监控连接使用情况
func MonitorConnections() {
stats := DB.Stats()
log.Printf("Open Connections: %d", stats.OpenConnections)
log.Printf("In Use: %d", stats.InUse)
log.Printf("Idle: %d", stats.Idle)
log.Printf("Wait Count: %d", stats.WaitCount)
log.Printf("Wait Duration: %v", stats.WaitDuration)
}
关键修改点:
- 设置
SetMaxOpenConns- 限制最大连接数,避免无限增长 - 使用
defer确保资源释放 - 即使在 panic 或错误情况下也能关闭资源 - 添加连接生命周期管理 - 防止连接泄漏
- 使用 context - 支持超时和取消
- 移除不必要的 prepared statement - 对于简单查询直接使用
QueryContext
对于每秒 50-60 个 API 调用,建议 SetMaxOpenConns 设置在 100-200 之间,具体数值需要根据实际负载测试调整。



