当前位置:首页 > 文章列表 > Golang > Go教程 > Go database/sql 怎样依次读取多个 SQL 结果集

Go database/sql 怎样依次读取多个 SQL 结果集

来源:17golang原创 2026-10-13 01:23:37 0浏览 收藏

Go 的 database/sql 读取多个 SQL 结果集,关键是把“行的移动”和“结果集的切换”分开:当前结果集内部反复调用 rows.Next(),每次成功后再 rows.Scan();当前结果集读完后,再调用 rows.NextResultSet() 进入下一组。切换成功后,仍然必须先调用一次 Next(),不能直接 Scan()。

最稳妥的顺序是:外层负责结果集,内层负责行;内层结束后检查当前行迭代状态,外层结束后再检查是否有切换错误。某一组没有行不等于没有下一组结果集。
要点速览
  • Next 只负责把游标移动到当前结果集的下一行。
  • NextResultSet 只负责准备下一组结果集,返回 false 时要结合 Rows.Err 判断原因。
  • 多结果集通常有不同列结构,不能为了复用变量而强行用同一组 Scan 参数。
  • 查询得到的 Rows 应及时关闭;迭代结束后仍要检查错误。
Go database/sql 中外层结果集游标和内层行游标的关系说明图
图1:外层用 NextResultSet 切换结果集、内层用 Next 读取行的结构说明图,不是运行截图。

先分清 Next 和 NextResultSet 各自移动什么

*sql.Rows 可以看成一组按顺序到达的结果集。每个结果集内部又有自己的行游标。刚拿到 Rows 时,游标在第一组结果集的第一行之前,所以第一次读取也要先调用 Next。

方法负责的层级返回 false 时要关注什么
Next()当前结果集中的下一行可能是当前组读完,也可能发生迭代错误,之后检查 Err()
Scan(...)读取已经由 Next 准备好的当前行目标变量数量和类型要匹配当前组的列
NextResultSet()下一组结果集可能没有下一组,也可能切换时发生错误,之后检查 Err()

官方文档还特别强调:调用 NextResultSet 后,扫描下一组前必须再次调用 Next;下一组结果集也可能完全没有行。因此,“上一组的 Next 返回 false”不能直接结束整个 Rows 的处理。

用两层循环依次读取全部结果集

如果各结果集的列结构相同,可以把每行先读成通用的 []any;如果列结构不同,则应在外层记录结果集编号,并为不同编号选择不同的 Scan 目标。下面的骨架只演示游标顺序,不绑定某个数据库的存储过程写法。

package main

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

// readAllResultSets 展示先读行、再切换结果集的固定顺序。
func readAllResultSets(ctx context.Context, db *sql.DB, query string) error {
    rows, err := db.QueryContext(ctx, query)
    if err != nil {
        return fmt.Errorf("query multiple result sets: %w", err)
    }
    defer rows.Close() // 无论中途哪一步失败,都释放 Rows 关联的资源。

    resultSetIndex := 0
    for {
        // 当前结果集可能为空;只有 Next 为 true 时才允许 Scan。
        for rows.Next() {
            var first, second any
            if err := rows.Scan(&first, &second); err != nil {
                return fmt.Errorf("scan result set %d: %w", resultSetIndex, err)
            }
            fmt.Printf("set=%d first=%v second=%v\n", resultSetIndex, first, second)
        }
        // Next 为 false 既可能是正常读完,也可能是迭代错误。
        if err := rows.Err(); err != nil {
            return fmt.Errorf("iterate result set %d: %w", resultSetIndex, err)
        }

        // 当前组读完后,才尝试准备下一组;返回 false 代表整个序列结束或切换失败。
        if !rows.NextResultSet() {
            if err := rows.Err(); err != nil {
                return fmt.Errorf("advance after result set %d: %w", resultSetIndex, err)
            }
            break
        }
        resultSetIndex++
    }
    return nil
}

这段代码的两个判断不能合并成“Next 返回 false 就退出”:内层循环结束只说明当前结果集暂时没有下一行,外层还要给 NextResultSet 一次机会。反过来,NextResultSet 返回 false 后,也不能直接把它当作成功结束,必须先看 Rows.Err()。

空结果集和不同列结构怎么处理

一个结果集可以没有任何行,但后面仍然存在下一组结果集。此时内层 for rows.Next() 一次都不进入,随后只要 rows.Err() 没有错误,仍然应该继续调用 NextResultSet()。

更常见的边界是:第一组返回用户列,第二组返回统计列,两组列数和类型不同。这时可以按结果集编号分支,但仍保持相同的游标顺序:

// processCurrentSet 按结果集编号选择 Scan 目标,避免列结构错配。
func processCurrentSet(rows *sql.Rows, setIndex int) error {
    for rows.Next() {
        switch setIndex {
        case 0:
            var id int64
            var name string
            if err := rows.Scan(&id, &name); err != nil {
                return fmt.Errorf("scan user set: %w", err)
            }
            fmt.Printf("user id=%d name=%s\n", id, name)
        case 1:
            var total int64
            if err := rows.Scan(&total); err != nil {
                return fmt.Errorf("scan summary set: %w", err)
            }
            fmt.Printf("total=%d\n", total)
        default:
            return fmt.Errorf("unexpected result set index %d", setIndex)
        }
    }
    // 当前结果集为空时,这里仍然返回 nil,是否还有下一组由调用方决定。
    return rows.Err()
}

如果结果集数量不是固定的,不能提前假设每一组都有两列。可以先读取 rows.Columns() 或 rows.ColumnTypes() 决定解析策略,但这些方法仍然只描述当前结果集;切换后需要重新读取列信息。

Go 多结果集读取中空结果集、Scan错误和NextResultSet错误的处理边界说明图
图2:空结果集、扫描失败和切换失败的处理边界说明图,不是运行截图。

循环结束后检查 Err 和 Close

Rows.Err() 负责报告遍历过程中遇到的错误,也可以在显式或隐式 Close 后调用。实务中建议同时保留 defer rows.Close() 和关键位置的 Err 检查:前者兜底释放资源,后者让错误尽早带着结果集编号返回。

如果主动在中途停止消费,例如只需要第一组结果集,应明确调用 rows.Close(),不要继续调用 NextResultSet。如果完整消费到最后一组,驱动可能在没有更多结果集时自动关闭,但显式关闭仍然能让生命周期更清楚。

排查顺序
  1. 确认驱动本身支持多结果集,并确认查询确实返回了多组。
  2. 确认每次 Scan 前都有一次成功的 Next。
  3. 确认当前组结束后调用了 NextResultSet,而不是重新执行查询。
  4. 分别在当前组结束和切换失败处检查 Rows.Err()。
  5. 确认不同结果集的列结构没有复用错误的 Scan 参数。

常见问题

NextResultSet 返回 false 就一定是没有下一组吗?

不一定。它既可能表示没有更多结果集,也可能表示切换过程中发生错误。应立即检查 rows.Err() 来区分。

空结果集需要单独调用什么方法吗?

不需要。内层 Next 直接返回 false 后,先检查 Err,再继续调用 NextResultSet 即可。

能不能在 NextResultSet 后直接 Scan?

不能。切换到下一组后,必须先调用 Next,因为游标仍然位于该组第一行之前。

官方 API 文档在哪里?

可直接查看 https://pkg.go.dev/database/sql#Rows.NextResultSet,重点关注 Next、NextResultSet、Err 和 Close 四个方法的关系。

小结:把 Rows 想成“结果集容器中的行游标”即可:内层 Next 处理行,外层 NextResultSet 处理组,Scan 永远跟在 Next 后面,错误统一通过 Err 收口。这样即使遇到空组、异构列或驱动切换错误,代码的控制流也不会混乱。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
栗子漫画常见问题怎么查?官方入口、公开资料页与反馈顺序说明栗子漫画常见问题怎么查?官方入口、公开资料页与反馈顺序说明
上一篇
栗子漫画常见问题怎么查?官方入口、公开资料页与反馈顺序说明
Go database/sql Scan 得到的 []byte 为什么不能直接长期保存
下一篇
Go database/sql Scan 得到的 []byte 为什么不能直接长期保存
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    446次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    517次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    532次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    478次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    303次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码