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)