当前位置:首页 > 文章列表 > Golang > Go教程 > Go database/sql 怎么连续读取存储过程返回的多个结果集

Go database/sql 怎么连续读取存储过程返回的多个结果集

来源:17golang原创 2026-11-05 08:54:14 0浏览 收藏

用 Go 调用一个会返回多组结果的查询或存储过程时,关键不是把所有数据塞进同一个 for rows.Next(),而是把 *sql.Rows 看成“当前结果集游标”。先用 Next 和 Scan 读完当前组,再用 NextResultSet 切到下一组;每次返回 false 后,都要结合 Rows.Err 判断究竟是正常结束还是发生了错误。

官方文档:https://pkg.go.dev/database/sql#Rows.NextResultSet

本文只讨论 Rows.NextResultSet、Next 和 Err 的调用顺序,不展开 MySQL、SQL Server 或其他数据库的存储过程语法。能否得到多组结果,仍取决于所使用的 database/sql 驱动。

先分清两个游标层级

Rows.Next 只在当前结果集内向前移动一行。它返回 true 时才能调用 Scan;返回 false 时,可能是当前结果集已经读完,也可能是读取过程中出现错误。

Rows.NextResultSet 负责准备下一组结果集。它返回 true 后,必须再次调用 Rows.Next,不能直接 Scan。下一组可能有行,也可能是空结果集,所以“切换成功”和“读到一行”是两个不同判断。

Go database/sql 中 Rows、Next、Scan 和 NextResultSet 的结果集关系结构图
图1:Rows 在当前结果集内逐行读取,再通过 NextResultSet 准备下一组;这是静态结构说明图,不是运行截图。

最小读取骨架应该怎么写

如果不同结果集的列数和列类型可能不同,最稳妥的通用入口是先读取当前组的列名,再按列数创建 Scan 目标。下面的示例把每个单元格转换成字符串,仅用于演示结果集边界;业务代码应根据已知列结构扫描到具体类型。

package multiset

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

// ReadResultSets 按结果集边界收集数据,不依赖具体存储过程语法。
func ReadResultSets(ctx context.Context, db *sql.DB, query string, args ...any) ([][][]string, error) {
	rows, err := db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("查询多结果集失败: %w", err)
	}
	defer rows.Close() // 无论中途哪一步失败,都把 Rows 交还给驱动。

	var resultSets [][][]string
	for {
		columns, err := rows.Columns()
		if err != nil {
			return nil, fmt.Errorf("读取当前结果集列信息失败: %w", err)
		}

		var currentSet [][]string
		for rows.Next() {
			values := make([]any, len(columns))
			dest := make([]any, len(columns))
			for i := range values {
				// Scan 需要接收每一列地址,列数必须与当前结果集一致。
				dest[i] = &values[i]
			}
			if err := rows.Scan(dest...); err != nil {
				return nil, fmt.Errorf("扫描第 %d 组结果失败: %w", len(resultSets)+1, err)
			}

			row := make([]string, len(values))
			for i, value := range values {
				// 这里只为统一返回类型;金额、时间等字段应使用业务类型。
				row[i] = fmt.Sprint(value)
			}
			currentSet = append(currentSet, row)
		}

		if err := rows.Err(); err != nil {
			// Next 返回 false 后先检查行迭代错误,再尝试切换结果集。
			return nil, fmt.Errorf("读取第 %d 组结果失败: %w", len(resultSets)+1, err)
		}
		resultSets = append(resultSets, currentSet)

		if !rows.NextResultSet() {
			// NextResultSet 的 false 既可能表示没有下一组,也可能表示推进失败。
			if err := rows.Err(); err != nil {
				return nil, fmt.Errorf("切换到下一组结果失败: %w", err)
			}
			break
		}
		// 切换成功后回到循环顶部,下一次 Next 才会读取新结果集的第一行。
	}

	return resultSets, nil
}

这个骨架有三个容易被忽略的点:Columns 和 Scan 都针对当前结果集;当前组结束后先查一次 Err;NextResultSet 返回 false 后还要再查一次 Err。

为什么不能把两个 Next 混成一个循环

单结果集代码通常只有一层循环:

for rows.Next() {
	var id int64
	var name string
	// 中文注释:当前结果集每成功移动一行,才能执行 Scan。
	if err := rows.Scan(&id, &name); err != nil {
		return err
	}
}
if err := rows.Err(); err != nil {
	return err
}

多结果集必须再加一层“结果集循环”。如果只调用一次 Next 循环,第一组读完后游标不会自动把第二组的列结构交给你的扫描代码;如果在 NextResultSet 后直接 Scan,则违反了 API 要求,正确顺序始终是“切换结果集,再 Next,再 Scan”。

空结果集和列结构变化怎么处理

某个结果集可以没有数据行。此时它的 for rows.Next() 一次也不进入,但只要 Rows.Err() 为 nil,就仍应继续调用 NextResultSet。不要用“当前组读取到的行数为 0”当作整个查询结束条件。

不同结果集的列数也可能不同。例如第一组返回用户编号和名称,第二组只返回汇总数量。若代码始终写成 rows.Scan(&id, &name),切换到第二组后就会因参数数量不匹配而失败。动态示例使用当前组的 Columns 解决了列数问题;业务代码则应根据结果集约定,在切换边界选择对应的结构体或字段集合。

Go database/sql 中结果集 A、NextResultSet、结果集 B 与 Err 错误分支的边界关系图
图2:逐行读取与结果集切换属于不同边界,Err 同时承担行迭代和切换失败的判别;这是静态结构说明图,不是运行截图。

Rows.Err 应该放在哪些位置

建议保留两个检查点:

  • 当前结果集的 Next 循环结束后检查一次,定位行迭代或驱动读取错误。
  • NextResultSet 返回 false 后再检查一次,区分“没有更多结果集”和“切换下一组失败”。

Rows.Close 可以用 defer 兜底。官方文档说明,当 Next 已返回 false 且没有后续结果集时,Rows 通常会自动关闭,但显式 defer rows.Close() 更能覆盖提前返回、扫描失败和驱动差异。

数据量大时不要无条件全部缓存

上面的通用函数返回 [][][]string,适合展示调用顺序,却会把所有结果集和所有行保存在内存中。生产代码如果只需要逐组写入文件、转换消息或汇总指标,可以在当前结果集的 Scan 成功后立即处理这一行,只保留当前组需要的状态;只有确实要在内存中统一返回时,才收集完整切片。

无论采用流式处理还是切片收集,都不要为了“快速拿到下一组”而跳过当前组的剩余行。先把当前组读完,再调用 NextResultSet,更容易保持连接和驱动状态的一致性。

驱动能力和调用边界

database/sql 定义了读取多个结果集的接口,但具体驱动是否支持、如何把存储过程结果暴露为多个结果集,仍由驱动实现决定。若 NextResultSet 很快返回 false,先查 Rows.Err;没有错误时,再检查驱动文档和查询本身是否确实返回了多组结果。

还要避免把“多结果集读取”误当成事务控制。是否需要事务、查询是否有副作用、超时和取消如何设置,属于调用方的业务决策;本文的核心只在 Rows 游标的两个层次。

一张表记住调用顺序

调用作用返回后要做什么
Next移动到当前结果集的下一行true 后才能 Scan,false 后查 Err
Scan把当前行列值写入目标变量目标数量必须匹配当前结果集列数
NextResultSet准备下一组结果集true 后必须重新调用 Next
Err报告行迭代或结果集推进错误在两个 false 边界都检查
Close结束 Rows 生命周期通常用 defer 兜底

相关问题

NextResultSet 返回 false 就一定是读完了吗?

不一定。它也可能表示切换时发生错误,必须紧接着调用 Rows.Err 区分两种情况。

切换结果集后可以直接 Scan 吗?

不可以。切换成功后必须先调用 Rows.Next,只有它返回 true 才能扫描当前行。

某组结果没有行时要不要跳过 NextResultSet?

不要。空结果集仍然是一个结果集边界,应在确认 Err 没有错误后继续尝试切换。

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