go使用mysql时query之后连接占用不释放的问题

415 阅读2分钟

1. 问题

exec、queryRow等方法不存在这个问题,主要就是query。
我们通常看到的说法都是在scan到eof标识之后会自动释放query占用的连接,即自动执行rows.Close(),但是有以下这几种情况,无法保证scan执行到最后。
可以通过mysql执行show processlist查看,被占用的连接状态为sleep,程序不结束就一直在,程序停止运行则会随着程序里的数据库连接池的释放一起释放。

1)使用query查询单行数据

sqlStr := "select *id, name, description from test_data"
test := TestData{}
var rows *sql.Rows
var err error
rows, err = Client.Query(sqlStr)
if err != nil {
	log.Fatal(err)
}
if rows.Next() {  // 这里本应该写 for rows.Next()
    err = rows.Scan(&test.id, &test.name, &test.description)
    if err != nil {
        log.Println(err)
    }
	log.Print(test)
}
time.Sleep(time.Hour*2) // 为了让程序不停止,方便查看数据库连接占用

因为知道查询结果是单行,所以使用if导致rows.Next()读取了第一行数据之后,没有第二次rows.Next(),scan没有读到eof,无法释放连接。

2)scan过程中发生panic

defer func() {
	if e := recover(); e != nil {
		log.Printf("发生panic,原因:%v", e)
		debug.PrintStack()
		time.Sleep(time.Hour * 2) // 为了让程序不停止,方便查看数据库连接占用
	}
}()

sqlStr := "select *id, name, description from test_data"
test := TestData{}
var rows *sql.Rows
var err error
rows, err = Client.Query(sqlStr)
if err != nil {
	log.Fatal(err)
}
for rows.Next() { 
    err = rows.Scan(&test.id, &test.name, &test.description)
    if err != nil {
        log.Println(err)
    }
	log.Print(test)
}

发生了panic导致scan没有执行到最后遇到eof,无法释放连接。

2. 解决办法

虽然有自动执行rows.Close()的机制在,但是并不完善,还是要手动加上。因为rows.Close()可以重复执行,不存在再次关闭导致panic的问题,所以在每个query结果下面都跟上

defer rows.Close()

但是仍要注意以下问题

sqlStr := "select * from test_log_data" // 不存在test_log_data表,err不为nil
test := TestLog{}
var rows *sql.Rows
var err error

rows, err = Client.Query(sqlStr)
defer rows.Close()
if err != nil {
    log.Println(err)
} else {
	for rows.Next() {
        err = rows.Scan(&test.id, &test.name, &test.description)
        if err != nil {
            log.Println(err)
        }
		log.Print(test)
	}
}

此时发生err,rows实际上为空指针,最后执行defer rows.Close()时就会报错panic: runtime error: invalid memory address or nil pointer dereference
应该把defer rows.Close()写在err为nil的判断后面。当然也可以这么写:

defer func() {
	if rows != nil {
		rows.Close()
	}
}()

注意不要写成

defer func() {
	if rows != nil {
		rows.Close()
	}
}(rows)