如何避免切片中多个 map 引用同一内存块

梦明小哥_1575

梦明小哥_1575

2026-04-08

564人浏览

原创

在 Go 中使用 sql.Rows.Scan 配合 []sql.RawBytes 时,若未正确处理底层字节引用,会导致多次 append 到切片的 map[string]interface{} 实际共享同一份 RawBytes 数据,造成所有 map 显示相同内容。

在 go 中使用 `sql.rows.scan` 配合 `[]sql.rawbytes` 时,若未正确处理底层字节引用,会导致多次 `append` 到切片的 `map[string]interface{}` 实际共享同一份 `rawbytes` 数据,造成所有 map 显示相同内容。

这个问题的根本原因在于:sql.RawBytes 是一个零拷贝的字节切片引用类型——它不持有数据副本,而是直接指向 rows.Scan 内部缓冲区中的原始内存地址。每次调用 rows.Next() + rows.Scan() 时,vals 切片中的 sql.RawBytes 会被复用并覆盖,而你存入 m[cols[i]] = vals[i] 的是 sql.RawBytes 本身(即一个 []byte header),其底层数组指针始终指向同一块被反复写入的内存。

因此,尽管你为每个循环创建了新的 map[string]interface{}(m := make(map[string]interface{})),但其中存储的 sql.RawBytes 值仍指向同一缓冲区;当后续行扫描覆盖 vals 时,之前所有 map 中的 RawBytes 内容也随之“静默更新”,最终 res 中所有 map 显示最后一行的数据。

✅ 正确做法:显式拷贝字节数据

你需要将 sql.RawBytes 转换为独立拥有的 []byte(即深拷贝),再存入 map:

for i := range vals {
    // ✅ 安全拷贝:创建新底层数组,与 rows 缓冲区解耦
    if len(vals[i]) > 0 {
        copied := make([]byte, len(vals[i]))
        copy(copied, vals[i])
        m[cols[i]] = copied
    } else {
        m[cols[i]] = []byte{} // 或 nil,根据业务需要
    }
}

? 补充说明:sql.RawBytes 的文档明确指出:“If an argument has type `RawBytes, Scan saves a reference to the underlying data — the caller must not modify or retain it beyond the next call toNext`.*” 这正是问题根源。

? 更简洁、推荐的替代方案(避免 RawBytes)

如果你不需要零拷贝优化(绝大多数 Web/API 场景都不需要),*直接使用 `interface{}或具体类型(如string,int64)作为 scan 目标**,让database/sql` 自动完成安全转换和内存分配:

func ResultRows(rows *sql.Rows, limit int) (DBResult, error) {
    cols, err := rows.Columns()
    if err != nil {
        return nil, err
    }

    // 使用 interface{} 指针数组,让 Scan 自动分配并复制值
    scanArgs := make([]interface{}, len(cols))
    for i := range scanArgs {
        scanArgs[i] = new(interface{}) // 每个字段独立分配
    }

    res := make(DBResult, 0, limit)
    for rows.Next() && len(res) <p>该方式无需手动管理字节拷贝,语义清晰,且天然规避引用复用问题;性能损耗可忽略(现代 GC 对小对象高效),是生产环境更健壮的选择。</p><h3>⚠️ 注意事项总结</h3>
  • ❌ 不要将 sql.RawBytes 直接存入长期存活的结构(如 slice/map)中;
  • ✅ 如必须用 RawBytes,务必 make([]byte, len(r)) + copy() 创建独立副本;
  • ✅ 优先使用 *interface{} 或具体类型扫描,由标准库保障安全性;
  • ✅ 总是检查 rows.Err() 并在循环后验证,避免遗漏错误;
  • ? 测试时可用 fmt.Printf("%p", &vals[0]) 观察地址是否变化,辅助定位复用问题。

通过以上任一方式修正,res 将如预期正确保存每行独立的 map 数据,彻底解决“所有元素显示最后一行”的引用陷阱。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
go语言 数组和切片
go语言 数组和切片

本专题整合了go语言数组和切片的区别与含义,阅读专题下面的文章了解更多详细内容。

2025.09.03

1792

14

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

2025.09.05

590

5

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

2025.11.16

343

7

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

2025.11.17

513

20

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

2025.11.27

283

6

OpenJDK JVM参数怎么调优
OpenJDK JVM参数怎么调优

Java应用运行缓慢,如何调整JVM参数?本专题介绍OpenJDK JVM性能调优的基本流程,涵盖性能指标确认、运行数据采集、内存配置和垃圾回收分析,帮助你先定位瓶颈,再通过对照测试验证效果,避免直接套用参数而影响应用稳定性。

2026.10.16

100

10

OpenJDK怎么配置Java开发环境
OpenJDK怎么配置Java开发环境

怎样用OpenJDK搭建开发环境?本专题介绍版本选择、下载安装、环境变量配置和编译运行验证,帮助你理清JDK的作用及基本配置流程,并通过简单程序检查环境是否可用,为后续项目开发做好准备。

2026.10.16

60

10

OpenJDK怎么部署Java应用
OpenJDK怎么部署Java应用

Java项目开发完成后,怎样部署到目标环境并正常运行?本专题介绍OpenJDK应用部署流程,涵盖运行环境准备、版本兼容检查、应用打包、启动参数配置和日志查看,帮助你理清部署前后的操作顺序,减少环境差异造成的运行问题。

2026.10.16

40

10

Revel框架模板渲染与表单验证
Revel框架模板渲染与表单验证

本专题围绕Revel框架视图层开发,介绍模板语法、变量输出、布局复用、表单提交、参数绑定与验证规则,帮助开发者完成安全规范的页面交互。

2026.10.16

40

15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.8万人学习