golang 调用mysql 连接数泄露的问题以及最大连接数和最大空闲连接数解释


1:golang mysql时,Prepare报错:dial tcp 127.0.0.1:3306: getsockopt: connection refused' 
解决办法:查看mysql初始化时候的用户名密码是否正确

2:mysql最大连接数和最大空闲连接数测试
测试程序:
package main

import (
  "fmt"
  "database/sql"
  _"github.com/go-sql-driver/mysql" //下划线为只引入,不调用其里面的任何函数,用到了里面的init函数进行驱动初始化
  "errors"
  "time"
)

var db *sql.DB
func initDB() (*sql.DB, error) {
    connectStr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?timeout=%dms&readTimeout=%dms&writeTimeout=%dms&charset=utf8", "用户名", "密码", "hostip", 端口, "库名", 1000, 500, 500)//后面三个分别为连接超时,读超时,写超时
    db, err := sql.Open("mysql", connectStr)
    if err != nil {
        fmt.Println("open mysql err:", err)
        return nil, err
    }
    if db == nil {
        fmt.Println("mysql connection err:")
        return nil, errors.New("Mysql Connection error")
    }
    db.SetMaxOpenConns(20)
    db.SetMaxIdleConns(0)
    db.Ping()
    return db, nil
}

func execSql() {
        var connection_id int
        err := db.QueryRow("select CONNECTION_ID()").Scan(&connection_id)
        if err != nil {
                fmt.Println("query connection id failed:", err)
                return
        }

        fmt.Println("connection id:", connection_id)
}

func UpdateStatus(status int, length float64) error {
    stat, err := db.Prepare(fmt.Sprintf("update %s set `status` = ?, `length` = ?, `finish_time` = ? where `requestId` = ?", "saas_video_req_list"))
    fmt.Println("stat:", stat)
    if err != nil {
        fmt.Println("prepareerr:", err)
        return err
    }
    defer stat.Close()
    requestId := "8888888888888888888"
    _, errExec := stat.Exec(status, length, time.Now().Format("2006-01-02 15:04:05"), requestId)
    if errExec != nil {
        fmt.Println("execerr:", errExec)
        return errExec
    }
    fmt.Println(length)
    return nil
}

func main() {

    var err error
    if db, err = initDB(); err != nil {
        fmt.Println("init db err:", err)
    }

    for i:=0; i< 15000; i++ {
        go UpdateStatus(2, float64(i))
        //execSql()

        //time.Sleep(time.Second*1)
    }
    time.Sleep(time.Second*10)
}

测试case1:测试连接泄露的情况
步骤1:查看一下mysql中设置的最大连接数
mysql> show variables like '%max_connections%';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 10000 |
+-----------------+-------+
1 row in set (0.00 sec)

mysql>

可以看到最大连接数为10000,所以将调用UpdateStatus函数的地方设置为15000个,并且注释掉函数中的defer stat.Close()一行,手动让连接数泄露,即我打开连接之后不关闭。即便打开之后关闭,同时打开的也不能超过数据库中的最大连接数,否则还是报如下信息。

可以看到在执行mysql建立新的连接的时候,会出现错误信息:
prepareerr: Error 1461: Can't create more than max_prepared_stmt_count statements (current value: 16382)
prepareerr: Error 1461: Can't create more than max_prepared_stmt_count statements (current value: 16382)
prepareerr: Error 1461: Can't create more than max_prepared_stmt_count statements (current value: 16382)
execerr: Error 1461: Can't create more than max_prepared_stmt_count statements (current value: 16382)
execerr: Error 1461: Can't create more than max_prepared_stmt_count statements (current value: 16382)
prepareerr: Error 1461: Can't create more than max_prepared_stmt_count statements (current value: 16382)
execerr: Error 1461: Can't create more than max_prepared_stmt_count statements (current value: 16382)

结论:1:在有连接泄露的情况下,如果当时同时连接的个数超过了数据库中的最大连接数,则会出现再进来的mysql执行失败的情况。很危险,如果线上同时出现的连接数超过的mysql设置的最大的连接数,则后面的mysql语句会执行失败。
      2:将main函数中的for循环改成小于10000时,即使不执行defer stat.Close(),也不会出现上面的mysql的错误信息。因为即便全不关,也不会超过10000,等超时时间(这个怎么看超时时间?)后,会自动关闭。
      3:将for循环改成2000,最大连接数为100(如果此值大于mysql的最大连接数,一样会出现连接数过多的错误),多次执行,不执行defer stat.Close()代码,也不会出现连接超过最大连接数的情况,说明连接数应该是会自动关闭,出了函数作用域或者程序结束的时候,连接会自动关闭。但是还是强烈建议手动关闭,因为如果线上线程数和机器数比较多,同时达到了最大连接数,一样会出事故(超时时间应该和initDB里面设置的超时时间没关系,超时时间的设置为去链接数据库的超时时间,参考链接:https://www.cnblogs.com/lanyangsh/p/11749270.html。)

测试case2:测试SetMaxOpenConns函数功能
步骤一:将最大连接数SetMaxOpenConns的参数设置为2,最大空闲连接数SetMaxIdleConns参数设置为0。在执行Exec会出现阻塞的情况,即如果要创建10个连接,目前最大连接数为2,则需要2的最大连接数空闲出来之后,才能给后一个连接使用。不会报错,只会阻塞。

例如如下代码中:如果设置最大连接数为1,第二个query语句会阻塞,第二个打印打印不出来,除非调研close先关闭第一个query

func querySql() {

    db.Query("select * from saas_video_req_list")

    fmt.Println("111111")

    _, err := db.Query("select * from saas_video_req_list") //此操作将一直阻塞

    fmt.Println("queryerr:", err)

}

但是调用Prepare却不会??按道理Prepare也会占有连接数。但是却没有阻塞?

     
测试case3:将最大连接数SetMaxOpenConns的参数设置为2,最大空闲连接数SetMaxIdleConns参数设置为0。调用execSql()时,可以看到每次打印的connection_id都不相同。

结论:如果不设置最大空闲连接数,则每次连接都创建新的connectid,可以通过打印的connection_id不同解释该结论
      
测试case4:将最大连接数SetMaxOpenConns的参数设置为10,最大空闲连接数SetMaxIdleConns参数设置为2。每隔1s调用execSql()时,可以看到每次打印的connection_id相同的2个。如果调用过快,最大空闲连接数不够用,同样会创建新的连接。

结论:如果连接不用了,最大空闲连接数还有空闲,则放入最大空闲连接数,以备下次使用。

case4的测试结果:

golang 调用mysql 连接数泄露的问题以及最大连接数和最大空闲连接数解释_第1张图片

 

综上:

现象1:当程序中的最大连接数的值大于数据库中最大连接数的值时,如果程序中存在没有close连接的情况,程序执行一段时间后会出现最大连接数过多的错误。

现象2:当程序中的最大连接数的值小于于数据库中最大连接数的值时,即使程序中存在没有close连接的情况,也不会出现最大连接数过多的情况(应该是大于程序中设置的最大连接数的连接会自动释放?)

1:程序中养成手动Close的习惯。即使全部Close,但是程序中同时连接数大于mysql的连接数,一样出现最大连接数错误,解决参看下面一条。

2:设置程序中的最大连接数小于mysql的最大连接数。这样连接数过多会阻塞在程序中,不会影响mysql。

3:设置最大空闲连接数,可以重复利用连接。

 

常见的几种错误和解决办法:

错误1:如果设置的最大连接数过大,有可能会报prepareerr: dial tcp 10.141.0.234:3306: socket: too many open files,表示你超过了机器可以创建的最大文件描述符,

解决办法:使用ulimit -n 65535修改一下可以创建的最大文件描述符。

错误2:Error 1040: Too many connections这个错误,则是你程序设置的最大连接数超过了数据库的最大连接数。

解决办法:增大数据库最大连接数,或者减少程序设置的最大连接数,一般程序中设置的最大连接数没必要太大。

错误3:Error 1461: Can't create more than max_prepared_stmt_count statements (current value: 16382)

解决办法:检查程序中是否有连接泄露,比如prepare了没有close这种。或者没有泄露的情况下,同时连接数据库的操作是不是超过了数据库的最大连接数。


参考连接:https://blog.csdn.net/lanyang123456/article/details/101947421

你可能感兴趣的:(golang)