一个容易被忽略的数据库Bug

今天review小组内其他几个成员的代码,发现了一个大家都在写的bug,向各位分享一下;

直接上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package sql_demo

import (
"database/sql"
"fmt"
)

func sql_demo() error {
db, err := sql.Open("mysql", "xxxx:xxxx@tcp(localhost:3306)/xxxx?charset=utf8")
if err != nil {
fmt.Println(err)
return err
}
defer db.Close()
rows, err := db.Query("select id,name from sql_demo")
if err != nil {
fmt.Println(err)
return err
}
defer rows.Close()
var id int
var name string
for rows.Next() {
rerr := rows.Scan(&id, &name)
if rerr == nil {
return rerr
}
}
return nil
}

找bug:

是不是没找到?没关系,我们看下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package sql_demo

// Next prepares the next result row for reading with the Scan method. It
// returns true on success, or false if there is no next result row or an error
// happened while preparing it. Err should be consulted to distinguish between
// the two cases.
//
// Every call to Scan, even the first one, must be preceded by a call to Next.
func (rs *Rows) Next() bool {
var doClose, ok bool
withLock(rs.closemu.RLocker(), func() {
doClose, ok = rs.nextLocked()
})
if doClose {
rs.Close()
}
return ok
}

简单翻译一下:当 no next result row或者遇到 error的时候,Next()都会返回false,
我们需要调用Err来区分这两种情况;
所以bug找到了,迭代过程中,当遇到error时,比如网络异常等等,此时数据可能只读到了一部分,rows.Next()会返回false,
直接return nil,会把异常遗漏掉,造成一个隐形bug;
正确做法:
return nil改为rows.Err()即可;