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

7 回复

感谢您的回复。我理解了关于 SetConnMaxLifetime() 的内容。还有其他可能导致连接泄漏的原因吗?

更多关于Golang中MySQL连接数过多问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


@ganesh_salunkhe,

你收到什么错误信息,以及错误出现在代码的哪一行?

您好,@ganesh_salunkhe 先生,我无法使用 Go 语言的 map 来获取数据库列的数据。请帮帮我。

是否存在其他可能导致连接泄漏的因素?

连接是通道,而会话是一种状态。一个连接可能包含多个会话。如果是连接问题,你可以通过某种方式关闭连接,以避免达到连接限制。

请指教。

每个连接都可以是“永久”的,可以设置会话时长限制,也可以立即关闭。您可以通过“连接池”来复用连接。

Go语言图标

管理连接 - Go编程语言

Go语言吉祥物

你收到了什么错误信息,以及在代码的哪一行

这意味着,如果你想获得帮助,就请准确且完整地回答所提出的问题。

始终发布确切的消息文本,为了准确性,请复制并粘贴。 你对自己认为它说了什么的解读,从来都不够准确到足以回答问题。

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)也是如此。

学会如何阅读理解并准确回答问题,比了解某些“月度语言”的晦涩语法更重要。

你的代码存在几个关键问题导致连接数过多:

主要问题分析:

  1. 没有正确关闭数据库资源 - 虽然你调用了 stmt.Close()rows.Close(),但在错误处理路径中这些资源没有被关闭
  2. 连接池配置不当 - 只设置了 SetMaxIdleConns,但没有设置 SetMaxOpenConns
  3. 每次查询都创建新的 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)
}

关键修改点:

  1. 设置 SetMaxOpenConns - 限制最大连接数,避免无限增长
  2. 使用 defer 确保资源释放 - 即使在 panic 或错误情况下也能关闭资源
  3. 添加连接生命周期管理 - 防止连接泄漏
  4. 使用 context - 支持超时和取消
  5. 移除不必要的 prepared statement - 对于简单查询直接使用 QueryContext

对于每秒 50-60 个 API 调用,建议 SetMaxOpenConns 设置在 100-200 之间,具体数值需要根据实际负载测试调整。

回到顶部