当前位置:首页 > 文章列表 > Golang > Go教程 > Go database/sql.Rows.NextResultSet 怎样读取多结果集

Go database/sql.Rows.NextResultSet 怎样读取多结果集

来源:17golang原创 2026-10-21 20:16:44 0浏览 收藏

Go 的 database/sql 读取多结果集时,关键不是把几次 Rows.Next 连在一起,而是分清两个层次:Rows.Next 只在当前结果集中向前移动,Rows.NextResultSet 才负责切换到下一个结果集。每次切换成功后,都要重新调用 Rows.Next,然后才能 Scan。

可以把写法记成一句话:先读完当前结果集,再用 NextResultSet 切换;任何返回 false 的地方,都在适当位置检查 Rows.Err。这样才能区分“确实没有更多数据”和“驱动器在迭代或切换时出错”。

先分清 Rows 的两个游标层次

*sql.Rows 是查询结果的迭代器。一个查询可能包含多个结果集,但当前时刻只有一个结果集处于可读取状态。

  • Rows.Next():准备当前结果集的下一行,返回 true 才能调用 Rows.Scan。
  • Rows.Scan(...):把当前行的列复制到目标变量,目标数量要和当前结果集的列数匹配。
  • Rows.NextResultSet():准备下一个结果集,返回 true 表示还有结果集可切换。
  • Rows.Err():返回迭代过程中遇到的错误,用来解释 Next 或 NextResultSet 返回 false 的原因。
database/sql 查询入口、当前结果集、Rows.NextResultSet 与 Rows.Err 的静态关系图
图1:查询入口、当前结果集与切换方法的静态关系说明图,不是运行截图。

按结果集分别扫描列

下面的示例假设具体数据库驱动器允许一次查询返回两个结果集:第一个结果集返回用户,第二个结果集返回该用户的订单。不同驱动器对多语句查询和多结果集的支持并不完全相同,因此示例重点放在 Rows 的读取契约,而不是某个驱动器的连接配置。

package main

import (
    "context"
    "database/sql"
    "fmt"
)

func readMultipleSets(ctx context.Context, db *sql.DB, userID int64) error {
    // 多结果集能力由具体驱动器决定;这里专注展示 Rows 的游标关系。
    const query = `
        SELECT id, name FROM users WHERE id = ?;
        SELECT id, total FROM orders WHERE user_id = ?;
    `

    rows, err := db.QueryContext(ctx, query, userID, userID)
    if err != nil {
        // 查询阶段失败时还没有可迭代的 Rows。
        return fmt.Errorf("查询多结果集失败: %w", err)
    }
    defer rows.Close() // 无论中途哪一步返回,都释放底层资源。

    // 第一组结果集:每次 Next 成功后,Scan 才能读取当前行。
    for rows.Next() {
        var id int64
        var name string
        if err := rows.Scan(&id, &name); err != nil {
            // Scan 错误直接结束当前读取,交给调用方处理。
            return fmt.Errorf("读取用户结果集失败: %w", err)
        }
        fmt.Printf("user=%d name=%s\\n", id, name)
    }
    if err := rows.Err(); err != nil {
        // false 可能是迭代错误,不能只当成正常结束。
        return fmt.Errorf("遍历用户结果集失败: %w", err)
    }

    // 切换成功后,必须重新调用 Next,再按第二组列结构 Scan。
    if !rows.NextResultSet() {
        if err := rows.Err(); err != nil {
            return fmt.Errorf("切换订单结果集失败: %w", err)
        }
        return fmt.Errorf("查询没有返回订单结果集")
    }

    for rows.Next() {
        var orderID int64
        var total float64
        if err := rows.Scan(&orderID, &total); err != nil {
            // 第二组结果集的列结构与第一组不同,目标变量也要随之改变。
            return fmt.Errorf("读取订单结果集失败: %w", err)
        }
        fmt.Printf("order=%d total=%.2f\\n", orderID, total)
    }
    if err := rows.Err(); err != nil {
        // 这里覆盖第二组结果集的迭代错误。
        return fmt.Errorf("遍历订单结果集失败: %w", err)
    }
    return nil
}

这个例子有两个容易写错的地方。第一,切换到第二组之后不能直接 Scan,因为官方约定是每次 NextResultSet 后都要先调用 Next。第二,每组结果集结束时都应该检查 Rows.Err;如果只检查最后一次,很难判断错误发生在第一组读取还是结果集切换阶段。

空结果集不等于切换失败

NextResultSet 返回 true 只说明下一个结果集存在,并不保证其中有行。切换成功后,下一次 Rows.Next 可能立即返回 false,这时要看 Rows.Err:没有错误表示当前结果集为空或已经正常读完,有错误才表示迭代过程中出现了问题。

多结果集 A 和 B、Rows.NextResultSet、Rows.Err 与 Close 的静态边界关系图
图2:结果集 A/B、游标切换、错误读取和资源关闭的静态关系说明图,不是运行截图。
func readUntilEnd(rows *sql.Rows) error {
    // 只演示结果集边界,不假设每个结果集一定包含数据行。
    for {
        for rows.Next() {
            // 不同结果集的列结构可能不同,真实代码应按已知结构分别 Scan。
            var value any
            if err := rows.Scan(&value); err != nil {
                return fmt.Errorf("扫描当前结果集失败: %w", err)
            }
        }
        if err := rows.Err(); err != nil {
            // 当前结果集读完后,先把迭代错误归因到当前边界。
            return fmt.Errorf("读取当前结果集失败: %w", err)
        }
        if !rows.NextResultSet() {
            // false 加 Err 为空,才表示没有更多结果集。
            return nil
        }
    }
}

生产代码通常不会把所有结果集都扫描成 any,因为不同结果集的列数和类型往往不同。更稳妥的方式是为每个已知结果集写一个小的读取函数,或者在切换前根据业务约定固定列结构。上面的循环只用来突出边界判断。

Rows.Err、Close 和驱动器支持怎么配合

场景应调用的接口判断重点
读取当前结果集的下一行Rows.Next → Rows.Scan只有 Next 返回 true 才能 Scan
切换到下一组结果Rows.NextResultSet切换后必须重新 Next
解释 false 的原因Rows.Err区分正常结束、空结果集和迭代错误
结束读取Rows.Close用 defer 保证资源释放;需要时单独处理 Close 错误

不要把“支持 database/sql”理解成“必然支持多结果集”。多结果集通常还受数据库协议、驱动器和连接参数影响。应用启动时应选定明确支持该查询形态的驱动器,并把错误处理写在结果集边界,而不是只在整个函数最后检查一次。

常见追问

调用 NextResultSet 后为什么还要调用 Next?

因为 NextResultSet 只准备下一个结果集,它不负责准备其中的第一行。按照 database/sql 的约定,任何 Scan 前都要先由 Next 成功定位一行。

NextResultSet 返回 false 就一定是没有更多结果集吗?

不一定。它也可能是在切换过程中遇到了错误。调用 Rows.Err 后,如果得到非空错误,就应该按失败处理;只有错误为空时,才可以把它当成正常结束。

结果集为空时要怎么写?

切换成功后让 Rows.Next 返回 false,再检查 Rows.Err。如果错误为空,就表示这一组没有可扫描的行,继续尝试 NextResultSet 即可。

参考资料:https://pkg.go.dev/database/sql#Rows.NextResultSet、https://pkg.go.dev/database/sql#Rows、https://go.dev/doc/database/querying。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
青灰月尘手机壁纸怎样制造安静负空间青灰月尘手机壁纸怎样制造安静负空间
上一篇
青灰月尘手机壁纸怎样制造安静负空间
Go runtime/metrics 怎样读取运行时指标样本
下一篇
Go runtime/metrics 怎样读取运行时指标样本
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    582次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    620次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    651次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    616次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    432次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码