go中database/sql默认query会内存爆掉,因驱动缓存全部结果;解法是sql层用游标或分页+rows.next()迭代,并用csv.newwriter手动write/flush避免writeall内存暴涨。

为什么 database/sql 默认查询会内存爆掉
Go 用 db.Query() 拉几百万行数据写 CSV,不加控制基本等于给 GC 下战书——所有 sql.Rows 结果默认被缓存到内存,底层驱动(比如 pgx 或 mysql)即使支持流式读取,Query() 也会等结果全回来才放行。
- 现象:
runtime: out of memory或进程 RSS 突增到几十 GB - 本质:没启用游标/流式读取,驱动把整张表 load 到内存再 parse
- 解法不是换 ORM,是换查询方式——必须用
db.QueryRow()或带FOR NO KEY UPDATE的显式游标?不,更简单:用db.Query()但配上游标语句 +Rows.Next()迭代,关键在 SQL 层就分页或声明游标 - PostgreSQL 示例:
DECLARE csv_cursor CURSOR FOR SELECT * FROM huge_table,再用FETCH 10000 FROM csv_cursor循环;MySQL 8.0+ 可用SELECT ... LIMIT offset, size配合无状态游标逻辑
encoding/csv 写超大文件必须绕开 WriteAll
csv.WriteAll() 会先把全部记录转成 [][]string 存内存,导出千万行时直接 OOM。真实场景里,你得自己管 writer 生命周期、缓冲和 flush。
- 必须用
csv.NewWriter()+ 手动Write()+ 定期Flush(),别依赖 defer - 缓冲区大小影响很大:
w := csv.NewWriter(bufio.NewWriterSize(file, 1(1MB 缓冲比默认 4KB 快 3–5 倍) - 每写 10000 行
w.Flush()一次,避免 OS 缓冲区积压;如果写到 pipe 或网络,还得检查w.Error() - 注意:CSV 字段含换行符或逗号时,
Write()自动加引号,但不会帮你转义双引号——得提前把"替成"",否则解析失败
并发写 CSV 不仅没用,反而拖慢
磁盘 I/O 是瓶颈,不是 CPU。多个 goroutine 同时往一个 *os.File 写,竞争 file.Write() 锁,还可能破坏 CSV 行边界。
方科网络ERP图文店II版为仿代码站独立研发的网络版ERP销售程序。本本版本为方科网络ERP图文店版的简化版,去除了部分不同用的功能,使得系统更加精炼实用。考虑到图文店的特殊情况,本系统并未制作出入库功能,而是将销售作为重头,使用本系统,可以有效解决大型图文店员工多,换班数量多,订单混杂不清的情况。下单、取件、结算分别记录操作人员,真正做到订单全程跟踪!无限用户级别,不同的用户级别可以设置不同的价
- 错误做法:
go writeChunk(chunk)+ channel 收集数据 → 行序错乱、换行丢失、性能下降 40%+ - 正确做法:单 goroutine 流式读 + 单 goroutine 流式写,中间用带缓冲 channel(如
ch := make(chan []string, 1000))解耦,但 buffer 不要过大 - 如果真要提速,优先考虑压缩:用
gzip.NewWriter()包一层csv.Writer,CPU 换 IO 减少 60%,且 gzip 本身支持流式 - 别碰
sync.Mutex锁 writer——csv.Writer不是线程安全的,锁了也白锁
空值、时间、字节切片字段怎么安全转字符串
数据库字段类型和 Go 类型不一一对应,直接 fmt.Sprintf("%v", v) 容易 panic 或输出不可读内容(比如 []uint8 打印成 [104 101 108 108 111])。
立即学习“go语言免费学习笔记(深入)”;
-
nil:用sql.NullString等类型 scan,判断.Valid,无效时写空字符串"",别写"<nil>"</nil> -
time.Time:必须格式化,t.Format("2006-01-02 15:04:05"),否则默认 String() 输出带时区和纳秒,CSV 解析器大概率报错 -
[]byte:如果是文本内容(如 JSON 字段),用string(b);如果是二进制(如图片 blob),要么跳过,要么 base64 编码——但 base64 会让 CSV 膨胀 33%,慎用 - 小陷阱:
int64超过 JavaScript Number.MAX_SAFE_INTEGER(2^53−1)时,前端 CSV 解析可能精度丢失,必要时转成字符串输出
事情说清了就结束。最常漏的是 SQL 层没启游标、CSV 写的时候忘了 flush、还有 time 和 []byte 的隐式转换——这三处一错,跑一小时才发现文件全是乱码或半截,比重写逻辑还耗神。









