go处理大csv文件需显式控制:用bufio.newreadersize设64kb缓冲防syscall卡顿和引号跨buffer;手动trim utf-8 bom;fieldsperrecord=-1应对字段数不固定;写入用csv.writer+bufio.writer+定期flush,禁用readall/writeall防oom;注意编码、分隔符、空值、时间格式等现实数据与rfc标准的落差。
go 处理大 csv 文件不难,但一不留神就 oom 或写错格式。核心是别让 encoding/csv 跟你“猜”数据——它只认标准 rfc 4180,不修脏数据、不自动跳 bom、不帮你分页,全得你显式控制。
csv.NewReader 必须套 bufio.Reader,且 BufferSize 得手动设
直接传 *os.File 给 csv.NewReader() 看似省事,实际底层用默认 4KB 缓冲,在机械盘或网络存储上 syscall 频繁,读 GB 文件会卡顿。更糟的是,若某行含被引号包裹的换行符(合法 CSV),缓冲区太小会导致引号跨 buffer,解析直接失败。
- 显式用
bufio.NewReaderSize(f, 64*1024),64KB 是多数场景的甜点值 - 遇到 Windows 记事本导出的 CSV,开头可能有 UTF-8 BOM(
\uFEFF),csv.Reader不跳过,得在读第一行前手动 trim:bytes.TrimPrefix(buf.Bytes(), []byte("\ufeff")) - 字段数不固定(比如备注列常含逗号/换行)?设
reader.FieldsPerRecord = -1,否则一行少个字段就 panic
写 CSV 别用 WriteAll,Write + Flush 才可控
csv.WriteAll() 会把所有记录先转成 [][]string 存内存,千万行 CSV 直接触发 OOM。真实导出必须自己管生命周期。
- 用
csv.NewWriter(bufio.NewWriterSize(file, 1024*1024)),1MB 缓冲比默认快 3–5 倍 - 每写 1000–10000 行调一次
w.Flush(),避免 OS 缓冲区积压;如果写入 pipe 或 HTTP response,每次w.Flush()后还得检查w.Error() - 字段含双引号?
csv.Writer不会自动转义,得提前把"替成"",否则 Excel 打开报错
数据库导出 CSV 时,Query() 默认吃光内存
db.Query() 拉几百万行,不是流式读取——驱动等结果全回来才放行,Rows.Next() 只是遍历缓存。现象是 RSS 突增几十 GB,最后 runtime: out of memory。
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
- PostgreSQL:用游标,
DECLARE csv_cursor CURSOR FOR SELECT ...,再FETCH 10000 FROM csv_cursor循环 - MySQL 8.0+:用
SELECT ... LIMIT ?, ?分页,配合context.WithTimeout防卡死 - 千万别并发写同一
*os.File——磁盘 I/O 是物理串行的,goroutine 越多竞争越激烈,还可能破坏行边界
中文乱码、空值、时间字段最容易踩坑
标准库只认 UTF-8。GBK/Big5 文件直接喂给 csv.NewReader,中文变 ???,字段还会错位。时间字段更是重灾区:CSV 里只是字符串,csv.Reader 不做任何转换,time.Parse 格式不对就 panic。
- 非 UTF-8 编码?用
golang.org/x/text/encoding先转码,再传给csv.NewReader - 数据库查出的
sql.NullString或niltime,别直接fmt.Sprintf("%v"),要判空:if v.Valid { writer.Write([]string{v.String}) } - Excel 导入时看似空的单元格,
GetCellValue可能返回""或" "(带空格),用strings.TrimSpace再判断
真正难的不是语法,而是每一处「默认行为」和「现实数据」的落差:BOM 是否存在、字段是否真用逗号分隔、空值怎么表示、时间格式是否统一。这些细节不提前对齐,跑起来不是丢数据就是崩进程。










