在Golang的数据库开发场景中,操作数据库时难免会遇到各类错误,比如连接失败、SQL语句语法错误、约束冲突、查询结果为空等,合理的错误处理逻辑是保障程序稳定运行的关键。不同的错误类型需要采用不同的应对方式,不能一概而论地直接抛出错误。

Golang数据库操作的常见错误类型
使用标准库database/sql操作数据库时,常见的错误可以分为以下几类:
- 连接类错误:比如数据库服务未启动、连接参数配置错误、连接超时等,这类错误通常在初始化数据库连接阶段出现。
- SQL执行类错误:比如SQL语句语法错误、表名或字段名不存在、权限不足等,在执行
Query、Exec方法时触发。 - 数据约束类错误:比如插入重复主键、违反非空约束、外键关联失败等,这类错误和具体的表结构约束相关。
- 结果处理类错误:比如查询结果为空时调用
Scan方法、字段类型和接收变量类型不匹配等。
基础错误处理实践
首先看最基础的数据库连接和查询的错误处理示例,这里使用MySQL作为演示数据库,需要先引入对应的驱动:
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 初始化数据库连接,这里故意写错连接参数模拟连接错误
db, err := sql.Open("mysql", "root:wrong_password@tcp(127.0.0.1:3306)/test_db")
if err != nil {
log.Printf("数据库连接初始化失败: %v", err)
return
}
// 验证连接是否可用
err = db.Ping()
if err != nil {
log.Printf("数据库连接验证失败: %v", err)
return
}
defer db.Close()
// 执行查询操作
rows, err := db.Query("SELECT id, name FROM user WHERE age > ?", 18)
if err != nil {
log.Printf("SQL查询执行失败: %v", err)
return
}
defer rows.Close()
// 遍历结果集
for rows.Next() {
var id int
var name string
// 处理Scan错误
err := rows.Scan(&id, &name)
if err != nil {
log.Printf("结果扫描失败: %v", err)
continue
}
fmt.Printf("id: %d, name: %sn", id, name)
}
// 检查遍历过程中是否有错误
if err := rows.Err(); err != nil {
log.Printf("结果集遍历错误: %v", err)
}
}
针对性的错误判断与处理
基础的错误处理只能知道操作失败,但无法判断具体是哪类错误,我们可以结合错误类型和错误码做更精细的处理。MySQL驱动返回的错误通常包含错误码,可以通过类型断言获取:
package main
import (
"database/sql"
"fmt"
"log"
"github.com/go-sql-driver/mysql"
)
func handleDBError(err error) {
if err == nil {
return
}
// 判断是否为MySQL驱动的错误
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
switch mysqlErr.Number {
case 1045:
log.Printf("数据库账号密码错误,错误码: %d", mysqlErr.Number)
case 1049:
log.Printf("数据库不存在,错误码: %d", mysqlErr.Number)
case 1062:
log.Printf("主键或唯一约束冲突,错误码: %d", mysqlErr.Number)
case 1146:
log.Printf("表不存在,错误码: %d", mysqlErr.Number)
default:
log.Printf("MySQL执行错误,错误码: %d, 错误信息: %s", mysqlErr.Number, mysqlErr.Message)
}
return
}
// 判断是否为查询结果为空的错误
if err == sql.ErrNoRows {
log.Printf("查询结果为空")
return
}
// 其他未知错误
log.Printf("未知数据库错误: %v", err)
}
func main() {
db, err := sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/test_db")
if err != nil {
handleDBError(err)
return
}
err = db.Ping()
if err != nil {
handleDBError(err)
return
}
defer db.Close()
// 模拟插入重复主键的操作
_, err = db.Exec("INSERT INTO user (id, name) VALUES (1, '张三')")
handleDBError(err)
}
事务中的错误处理
在事务操作中,错误处理需要更加谨慎,一旦出现错误需要回滚事务,避免数据不一致:
package main
import (
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
func transferMoney(db *sql.DB, fromID, toID int, amount float64) error {
// 开启事务
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("开启事务失败: %v", err)
}
// 确保事务最终回滚或提交
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p)
} else if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
// 扣减转出账户余额
_, err = tx.Exec("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromID)
if err != nil {
return fmt.Errorf("扣减余额失败: %v", err)
}
// 增加转入账户余额
_, err = tx.Exec("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toID)
if err != nil {
return fmt.Errorf("增加余额失败: %v", err)
}
return nil
}
func main() {
db, err := sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/test_db")
if err != nil {
log.Printf("数据库连接失败: %v", err)
return
}
defer db.Close()
err = transferMoney(db, 1, 2, 100.0)
if err != nil {
log.Printf("转账操作失败: %v", err)
return
}
log.Printf("转账操作成功")
}
错误处理的最佳实践建议
- 不要忽略任何数据库操作返回的错误,即使是
rows.Close()这类方法,虽然通常不需要处理,但如果有特殊需求也需要关注。 - 对常见的业务错误做类型判断,返回更友好的错误提示,而不是直接抛出底层错误。
- 事务操作中一定要保证错误发生时回滚事务,避免脏数据产生。
- 可以在项目封装统一的数据库操作层,将错误处理逻辑收敛,减少重复代码。
- 对于查询结果为空的情况,不要当作错误处理,而是做正常的业务逻辑判断,比如返回空列表而不是报错。
GolangSQL查询异常处理database_sql错误捕获修改时间:2026-06-24 21:24:36