几乎所有使用 database/sql
类型的操作都将错误作为最后一个值返回。您应该始终检查这些错误,永远不要忽略它们。
在一些地方,错误行为是特殊情况,或者您可能需要了解一些额外信息。
来自迭代结果集产生的错误
考虑以下代码:
for rows.Next() {
// ...
}
if err = rows.Err(); err!= nil {
// 在这里处理错误
}
来自 rows.Err()
的错误可能是 rows.Next()
循环中各种错误的结果。除了正常完成循环外,循环可能出于其他原因退出,因此您始终需要检查循环是否正常终止。异常终止会自动调用 rows.Close()
,尽管多次调用无害。
来自关闭结果集产生的错误
如前所述,如果过早退出循环,则应始终显式关闭sql.Rows
。如果循环正常退出或由于错误退出,它将自动关闭,但您可能会错误地这样做:
for rows.Next() {
// ...
break; // 糟糕,行未关闭!内存泄漏...
}
// 执行通常的 "if err = rows.Err()" [此处省略] ...
// 在这里 [re?] 关闭总是安全的:
if err = rows.Close(); err != nil {
// 但是如果出现错误该怎么办?
log.Println(err)
}
由 rows.Close()
返回的错误是常规规则的唯一例外,该常规规则是最好捕获并检查所有数据库操作中的错误。如果 rows.Close()
返回一个错误,则不清楚应该做什么。记录错误消息或 panicing 可能是唯一明智的选择,如果这不明智,那么也许您应该忽略该错误。
来自 QueryRow() 的错误
考虑以下获取单行的代码:
var name string
err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
if err != nil {
log.Fatal(err)
}
fmt.Println(name)
如果没有 id = 1
的用户怎么办?那么结果中就没有行了,而且 .Scan()
也不会将值扫描到 name
中。那会发生什么呢?
Go 定义了一个特殊的错误常量,称为 sql.ErrNoRows
,当结果为空时从 QueryRow()
返回该常量。在大多数情况下,这需要作为特殊情况进行处理。应用程序代码通常不会将空结果视为错误,如果不检查错误是否为这个特殊常量,就会导致意想不到的应用程序代码错误。
来自查询的错误会被推迟到调用 Scan()
时,然后再返回。上面的代码最好这样写:
var name string
err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
if err != nil {
if err == sql.ErrNoRows {
//没有行,但也没有错误发生
} else {
log.Fatal(err)
}
}
fmt.Println(name)
有人可能会问,为什么将空结果集视为错误。空集没有任何错误。原因是 QueryRow()
方法需要使用此特殊情况,以便让调用者区分 QueryRow()
是否真的找到了一行;如果没有它,Scan()
将无法执行任何操作,您可能不会意识到您的变量根本没有从数据库中获得任何值。
只有在使用 QueryRow()
时才会遇到这个错误。如果您在其他地方遇到此错误,则说明您做错了什么。
识别特定的数据库错误
编写如下代码可能很有诱惑力:
rows, err := db.Query("SELECT someval FROM sometable")
// 错误包含:
// ERROR 1045 (28000):Access denied for user 'foo'@'::1' (using password: NO)
if strings.Contains(err.Error(), "Access denied") {
// 处理被拒绝的错误
}
然而,这并不是最好的方法。例如,字符串值可能会有所不同,具体取决于服务器使用哪种语言发送错误消息。最好通过比较错误编号来确定具体的错误是什么。
但是,执行此操作的机制因驱动程序而异,因为它本身不是 database/sql
的一部分。在本教程重点关注的 MySQL 驱动程序中,您可以编写以下代码:
if driverErr, ok := err.(*mysql.MySQLError); ok { // 现在可以直接访问错误编号
if driverErr.Number == 1045 {
// 处理被拒绝的错误
}
}
同样,这里的 MySQLError
类型是由这个特定的驱动程序提供的,而且 .Number
字段可能在不同的驱动程序之间有所不同。然而,该数字的值是从 MySQL 的错误消息中获取的,因此是特定于数据库的,而不是特定于驱动程序的。
这段代码仍然很难看。与 1045(一个魔术般的数字)相比,这是一种代码味道。一些驱动程序(虽然不是 MySQL 驱动程序,但由于一些与本文无关的原因) 提供了错误标识符列表。例如,Postgres pq
驱动程序可以在 error.go 中使用。还有一个由 VividCortex 维护的 MySQL 错误编号 的外部包。使用这样的列表,可以更好地编写上面的代码,如下所示:
if driverErr, ok := err.(*mysql.MySQLError); ok {
if driverErr.Number == mysqlerr.ER_ACCESS_DENIED_ERROR {
// 处理被拒绝的错误
}
}
处理连接错误
如果您与数据库的连接被删除、终止或出现错误怎么办?
发生这种情况时,您不需要实施任何逻辑来重试失败的语句。作为 database/sql
中 连接池 的一部分,内置了对失败连接的处理。如果您执行查询或其他语句,而底层连接失败,那么 Go 将重新打开一个新连接 (或仅从连接池获取另一个连接),然后重试,最多重试 10 次。
然而,这可能会有一些意想不到的后果。当其他错误情况发生时,可能会重试某些类型的错误。这也可能是特定于驱动程序的。MySQL 驱动程序发生的一个示例是,使用 kill
取消不需要的语句 (如长时间运行的查询) 会导致该语句最多重试 10 次。
转自:https://learnku.com/docs/go-database-sql/errors/9480