如何使用Golang处理数据库操作错误_GolangSQL查询异常处理实践

发布时间 - 2025-12-31 00:00:00    点击率:
Go 的 database/sql 错误不为 nil 不一定代表失败,需区分错误来源:Query/QueryRow 的 err 仅表示 SQL 提交是否成功,rows.Next() 返回 false 后须调用 rows.Err() 判断是否真出错,Scan 和 Close 也可能返回 error,事务中 Rollback 和 Commit 均需显式检查错误。

Go 的 database/sql 错误不是 nil 就代表失败?

很多刚写 Go 数据库代码的人会误以为只要 err != nil 就一定出错了,其实不然。比如调用 rows.Next() 返回 false 可能只是查不到数据,也可能是底层驱动报错;而这个错误要等到你调用 rows.Err() 才能拿到。漏掉这一步,就会让 SQL 执行失败却“静默”通过。

正确做法是:每次遍历完 rows 后必须检查 rows.Err(),且不能只依赖 QueryRow().Scan() 的返回 err —— 它只捕获查询发起阶段的错误(如语法错、连接断),不包含扫描时的类型不匹配或空值处理异常。

  • Query()QueryRow() 返回的 err 仅表示“SQL 是否成功提交”,不代表结果能正常读取
  • rows.Scan() 失败不会自动终止循环,需在每次调用后检查其返回 err
  • rows.Close() 也可能返回 error(如网络中断导致清理失败),建议 defer 后仍做一次显式检查

如何区分 sql.ErrNoRows 和其他数据库错误?

sql.ErrNoRows 是唯一一个被标准库明确定义的“预期性错误”,它不是 bug,而是业务常态(例如查用户不存在)。直接用 if errors.Is(err, sql.ErrNoRows) 判断最稳妥,避免字符串匹配或 == 比较 —— 不同驱动返回的具体 error 类型可能不同(如 pgx 返回的是自定义类型)。

var user User
err := db.QueryRow("SELECT name, age FROM users WHERE id = $1", 123).Scan(&user.Name, &user.Age)
if err != nil {
    if errors.Is(err, sql.ErrNoRows) {
        // 用户不存在,业务逻辑继续
        return nil, nil
    }
    // 其他错误:字段类型不匹配、NULL 写入非指针等
    return nil, fmt.Errorf("query user: %w", err)
}
return &user, nil

注意:只有 QueryRow().Scan() 会返回 sql.ErrNoRowsQuery() + rows.Next() 不会,它只会让 Next() 返回 false,此时你要靠 rows.Err() 判断是否真出错。

立即学习“go语言免费学习笔记(深入)”;

事务中遇到错误,tx.Rollback() 为什么还 panic?

常见写法是 defer tx.Rollback() 然后在中间 commit,但这样非常危险:如果 Rollback() 自己失败(比如连接已断),会触发 panic。更糟的是,panic 可能掩盖原本的业务错误。

正确模式是显式控制 rollback,并只在未 commit 时才调用:

tx, err := db.Begin()
if err != nil {
    return err
}
defer func() {
    if r := recover(); r != nil {
        tx.Rollback()
        panic(r)
    }
}()

// 执行操作...
_, err = tx.Exec("INSERT INTO orders (...) VALUES (...)", ...)
if err != nil {
    tx.Rollback() // 显式回滚
    return fmt.Errorf("insert order: %w", err)
}

err = tx.Commit()
if err != nil {
    tx.Rollback() // commit 失败也要尝试回滚
    return fmt.Errorf("commit transaction: %w", err)
}

关键点:

  • 不要用 defer tx.Rollback() 无条件回滚,它无法区分“该回滚”和“已经 commit”
  • tx.Commit()tx.Rollback() 都可能返回 error,尤其是网络不稳定时
  • 如果使用 sqlxent 等 ORM,它们内部通常已封装了安全的事务控制,但底层逻辑仍遵循这一原则

database/sql 处理 NULL 值时最容易踩的坑

Go 的原生 Scan 不支持直接把数据库 NULL 映射到普通类型(如 intstring),强行这么做会导致 sql.ErrNoRows 以外的 panic 或静默截断。必须用指针或 sql.Null* 类型。

推荐优先使用 sql.NullStringsql.NullInt64 等,它们自带 Valid 字段,语义清晰:

var name sql.NullString
var age sql.NullInt64
err := row.Scan(&name, &age)
if err != nil {
    return err
}
if name.Valid {
    fmt.Println("Name:", name.String)
} else {
    fmt.Println("Name is NULL")
}

如果你用 struct + Scan,别忘了字段必须是导出的(首字母大写),且类型要严格匹配 —— sql.NullString 不能 Scan 进 *string,反之亦然。

另一个隐形陷阱:SELECT COALESCE(col, '') FROM t 看似绕过 NULL,但会丢失原始列的 NULL 语义,影响后续聚合或索引优化,不如在 Go 层明确处理。

真正难的不是写 if err != nil,而是想清楚这个 err 到底来自哪一层:驱动连接?SQL 解析?权限校验?类型转换?还是事务状态不一致?每种都需要不同的恢复策略。


# go  # golang  # 标准库  # 为什么  # sql  # String  # NULL  # if  # 封装  # select  # Error  # 字符串  # int  # 循环  # 指针  # Struct  # nil  # 类型转换  # database  # 数据库  # bug  # 的是  # 不存在  # 会让  # 它只  # 不匹配  # 判断是否  # 这一  # 尤其是  # 也要  # 遍历 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel Pest测试框架怎么用_从PHPUnit转向Pest的Laravel测试教程  如何用花生壳三步快速搭建专属网站?  Linux后台任务运行方法_nohup与&使用技巧【技巧】  详解免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)  Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】  如何在万网自助建站平台快速创建网站?  韩国服务器如何优化跨境访问实现高效连接?  Firefox Developer Edition开发者版本入口  Laravel如何使用软删除(Soft Deletes)功能_Eloquent软删除与数据恢复方法  如何做网站制作流程,*游戏网站怎么搭建?  如何用虚拟主机快速搭建网站?详细步骤解析  香港代理服务器配置指南:高匿IP选择、跨境加速与SEO优化技巧  如何用JavaScript实现文本编辑器_光标和选区怎么处理  高防网站服务器:DDoS防御与BGP线路的AI智能防护方案  齐河建站公司:营销型网站建设与SEO优化双核驱动策略  php 三元运算符实例详细介绍  Laravel如何生成和使用数据填充?(Seeder和Factory示例)  Android利用动画实现背景逐渐变暗  香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化  如何用低价快速搭建高质量网站?  javascript日期怎么处理_如何格式化输出  Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧  Laravel怎么防止CSRF攻击_Laravel CSRF保护中间件原理与实践  Laravel表单请求验证类怎么用_Laravel Form Request分离验证逻辑教程  韩国代理服务器如何选?解析IP设置技巧与跨境访问优化指南  微信公众帐号开发教程之图文消息全攻略  Laravel Livewire是什么_使用Laravel Livewire构建动态前端界面  百度输入法ai组件怎么删除 百度输入法ai组件移除工具  详解Android——蓝牙技术 带你实现终端间数据传输  详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)  html5如何设置样式_HTML5样式设置方法与CSS应用技巧【教程】  制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?  Laravel如何使用Service Provider注册服务_Laravel服务提供者配置与加载  悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音  深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?  javascript和jQuery中的AJAX技术详解【包含AJAX各种跨域技术】  Laravel怎么创建控制器Controller_Laravel路由绑定与控制器逻辑编写【指南】  如何用腾讯建站主机快速创建免费网站?  微信小程序 canvas开发实例及注意事项  香港服务器部署网站为何提示未备案?  Laravel如何使用Vite进行前端资源打包?(配置示例)  如何用搬瓦工VPS快速搭建个人网站?  微博html5版本怎么弄发语音微博_语音录制入口及时长限制操作【教程】  QQ浏览器网页版登录入口 个人中心在线进入  javascript基于原型链的继承及call和apply函数用法分析  潮流网站制作头像软件下载,适合母子的网名有哪些?  佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】  夸克浏览器网页跳转延迟怎么办 夸克浏览器跳转优化  Laravel如何实现多语言支持_Laravel本地化与国际化(i18n)配置教程  PythonWeb开发入门教程_Flask快速构建Web应用