sql.Rows.Next 返回 false 后还要检查什么错误?
使用 Go 的 database/sql 读取多行结果时,rows.Next() 返回 false 不能直接当成“查询正常结束”。它同时表示“没有下一行”或“准备下一行时出现错误”,因此循环结束后必须检查 rows.Err()。最小且可靠的判断顺序是:循环内处理 Next 和 Scan,循环后检查 Err,并保证 Rows 能够关闭。
官方地址:https://pkg.go.dev/database/sql
如果只写了 for rows.Next() { ... },网络中断、上下文取消、驱动读取失败等情况就可能被当成“数据已经读完”,最后返回一份不完整但看起来正常的结果。
Next 返回 false 到底代表什么
Rows.Next 负责把游标推进到下一行。返回 true 时,当前行可以交给 Scan;返回 false 时,既可能是没有下一行,也可能是推进过程中发生了错误。它本身只返回布尔值,不携带错误详情。
| 现象 | 应该查看的对象 | 可以得出的结论 |
|---|---|---|
Next() == true | 继续调用 Scan | 当前行已经准备好 |
Next() == false 且 Err() == nil | 循环正常结束 | 没有更多可读行 |
Next() == false 且 Err() != nil | 返回或包装 Err() | 读取过程失败,结果不能当成完整结果使用 |

循环结束后先检查 rows.Err
官方推荐的写法是把 rows.Err() 放在遍历循环之后。这样既不会把每一轮的正常读取和最终的迭代错误混在一起,也能保留驱动或上下文取消提供的具体错误。
func loadNames(ctx context.Context, db *sql.DB) ([]string, error) {
// 让数据库读取跟随调用方的取消和超时信号。
rows, err := db.QueryContext(ctx, `SELECT name FROM users ORDER BY id`)
if err != nil {
return nil, fmt.Errorf("执行查询失败: %w", err)
}
// 无论是正常返回还是中途出错,都要释放结果集资源。
defer rows.Close()
var names []string
for rows.Next() {
var name string
// Scan 只负责当前行的列映射,错误要立即停止读取。
if err := rows.Scan(&name); err != nil {
return nil, fmt.Errorf("读取当前行失败: %w", err)
}
names = append(names, name)
}
// Next 为 false 后,用 Err 区分读完和读取失败。
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历结果集失败: %w", err)
}
return names, nil
}
这里的关键点是:Scan 错误和 rows.Err 错误处在不同阶段。前者表示当前行无法映射到目标变量,后者表示继续推进结果集时出现了错误。两者都应该让调用方知道本次结果不能按完整数据处理。
为什么还要写 defer rows.Close
遍历到最后一行后,标准库通常会自动关闭结果集;但函数也可能在循环中因为 Scan 错误、业务过滤或上层取消提前返回。入口处立刻注册 defer rows.Close(),可以覆盖这些提前退出路径。
如果使用的场景涉及批量语句、写入型结果或驱动明确提供了关闭错误,还可以把 Close 的返回值单独处理。需要注意的是,Close 不能代替 Err:前者处理资源关闭,后者报告遍历期间记录的错误。
func readWithClose(ctx context.Context, db *sql.DB) (err error) {
// 查询成功后立刻登记兜底关闭动作。
rows, err := db.QueryContext(ctx, `SELECT id FROM jobs`)
if err != nil {
return fmt.Errorf("查询任务失败: %w", err)
}
defer func() {
// 只有主体没有更早返回错误时,才把 Close 错误作为最终错误。
if closeErr := rows.Close(); err == nil && closeErr != nil {
err = fmt.Errorf("关闭结果集失败: %w", closeErr)
}
}()
for rows.Next() {
var id int64
// 这里只演示读取阶段,真实业务应继续处理 id。
if scanErr := rows.Scan(&id); scanErr != nil {
return fmt.Errorf("读取任务编号失败: %w", scanErr)
}
}
// 先保存遍历错误,避免 Close 的清理逻辑覆盖它。
if err := rows.Err(); err != nil {
return fmt.Errorf("遍历任务失败: %w", err)
}
return nil
}
上下文取消时不要把部分结果当成功
使用 QueryContext 时,调用方取消上下文或超时,驱动可能在下一次读取时返回错误。此时循环可能只是提前停止,调用方仍然需要通过 rows.Err() 识别失败。不要根据已经追加到切片里的行数判断查询是否完整,也不要因为切片非空就返回成功。
func loadPage(ctx context.Context, db *sql.DB) ([]int64, error) {
// 让调用方能够在请求结束时停止数据库读取。
rows, err := db.QueryContext(ctx, `SELECT id FROM events ORDER BY id`)
if err != nil {
return nil, err
}
defer rows.Close()
var ids []int64
for rows.Next() {
var id int64
// 当前行可读不等于整个结果集已经完整读完。
if err := rows.Scan(&id); err != nil {
return nil, err
}
ids = append(ids, id)
}
if err := rows.Err(); err != nil {
// 取消或网络读取错误时,丢弃部分结果并把错误交给上层。
return nil, fmt.Errorf("结果集未完整读取: %w", err)
}
return ids, nil
}
多结果集还要检查 NextResultSet
当驱动支持多结果集时,第一组数据读完后通常还要调用 NextResultSet。它同样只返回布尔值:没有下一组结果和切换过程中发生错误,都可能得到 false。因此每次结果集遍历完成后,最终仍要检查 rows.Err()。
func readSets(ctx context.Context, db *sql.DB) error {
// 多语句是否可用取决于数据库和驱动配置,这里只演示判断顺序。
rows, err := db.QueryContext(ctx, `SELECT id FROM a; SELECT id FROM b`)
if err != nil {
return err
}
defer rows.Close()
for {
for rows.Next() {
var id int64
// 每进入一个结果集,都必须先 Next 再 Scan。
if err := rows.Scan(&id); err != nil {
return fmt.Errorf("扫描结果集失败: %w", err)
}
}
// 当前结果集结束后先检查迭代错误。
if err := rows.Err(); err != nil {
return fmt.Errorf("读取结果集失败: %w", err)
}
// false 可能表示没有下一组,也可能表示切换失败。
if !rows.NextResultSet() {
if err := rows.Err(); err != nil {
return fmt.Errorf("切换结果集失败: %w", err)
}
return nil
}
}
}

把这条检查规则记成一个固定模板
对于普通的单结果集查询,可以把判断收敛成四句话:查询调用先检查返回错误;循环中每次 Next 为真才调用 Scan;循环结束后检查 rows.Err;函数退出前保证 rows.Close。如果是可取消请求,就使用 QueryContext;如果是多结果集,就在 NextResultSet 返回假之后再次查看 rows.Err。
最容易遗漏的不是 Scan,而是把 Next 返回 false 当作成功结束。只要结果可能受到网络、驱动、上下文或多结果集切换影响,rows.Err() 就是判断这次读取是否完整的最后一道检查。
相关问题
- 空结果集会让 rows.Err 返回错误吗? 不会。没有任何行但正常结束时,
Next返回false,Err通常仍为nil。 - QueryRow 没有结果时也检查 Rows.Err 吗? 不需要。
QueryRow返回的是*sql.Row,应检查Scan返回的sql.ErrNoRows或其它错误。 - 只调用 rows.Close 能发现读取错误吗? 不能。关闭负责资源边界,遍历错误要通过
rows.Err获取。
runtime finalizer 为什么不能当作确定性清理机制
- 上一篇
- runtime finalizer 为什么不能当作确定性清理机制
- 下一篇
- 事务 Commit 失败时为什么不能简单忽略回滚
-
- Golang · Go问答 | 8分钟前 | 连接池 · 性能排查 · database/sql · Go问答 · Go 连接池 MaxOpenConns MaxIdleConns database/sql sql.DB DB.Stats
- 数据库连接池等待过久时应检查哪些参数
- 476浏览 收藏
-
- Golang · Go问答 | 10分钟前 | 错误处理 · 连接池 · 工程实践 · Go问答 · Go 连接池 事务 database/sql Tx.Commit Tx.Rollback
- 事务 Commit 失败时为什么不能简单忽略回滚
- 489浏览 收藏
-
- Golang · Go问答 | 21分钟前 | 垃圾回收 · 工程实践 · Go问答 · 运行时 · Go 垃圾回收 资源清理 runtime.SetFinalizer runtime.KeepAlive
- runtime finalizer 为什么不能当作确定性清理机制
- 233浏览 收藏
-
- Golang · Go问答 | 23分钟前 | 性能优化 · Go问答 · Go 性能优化 逃逸分析 并发map heap allocation
- 逃逸分析显示 heap allocation 时如何减少分配
- 422浏览 收藏
-
- Golang · Go问答 | 27分钟前 |
- 并发读写 map 为什么会直接触发运行时错误
- 302浏览 收藏
-
- Golang · Go问答 | 32分钟前 |
- comparable 约束无法接收结构体时的判断方法
- 198浏览 收藏
-
- Golang · Go问答 | 36分钟前 |
- 接口类型集合为何不能直接作为变量类型
- 142浏览 收藏
-
- Golang · Go问答 | 38分钟前 |
- 泛型方法使用指针接收者时接口为何不匹配?
- 475浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 1010次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1014次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1139次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1110次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 955次使用
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- Go types.Selection.Type 与 Obj.Type 为什么不总是相同
- 2026-11-10 501浏览
-
- Go flag.FlagSet ContinueOnError 怎么捕获帮助输出和解析错误
- 2026-11-05 501浏览
-
- Go netip.ParseAddr 为什么拒绝带端口字符串
- 2026-11-03 501浏览

