Go标准库encoding/csv需手动处理BOM、缓冲区、字段映射、分批写入等细节,否则生产环境易出现Excel乱码、错行、OOM等问题。

Go 标准库的 encoding/csv 足够用,但直接传 *os.File、用 WriteAll、忽略 BOM 和字段转义,90% 的导入导出会在生产环境出问题——不是代码跑不通,而是 Excel 打不开、中文变方块、某行数据错位、百万行 OOM。
csv.NewReader 必须套 bufio.Reader 并设缓冲区大小
默认 4KB 缓冲在读大文件时 syscall 频繁,机械盘或网络存储上明显卡顿;更危险的是,若某行含被引号包裹的换行符(合法 CSV),小缓冲会导致引号跨 buffer,解析直接失败。
- 显式创建:
buf := bufio.NewReaderSize(f, 64*1024),64KB 是多数场景的甜点值 - 遇到 Windows 记事本导出的 CSV,开头可能带 UTF-8 BOM(
\uFEFF),csv.Reader不自动跳过,需在首次Read()前手动处理:bytes.TrimPrefix(buf.Bytes(), []byte("\ufeff")) - 字段数不固定(如备注列含逗号或换行)?设
reader.FieldsPerRecord = -1,否则少一个字段就 panic
写 CSV 不能用 WriteAll,必须自己控生命周期
csv.WriteAll() 会把所有记录先转成 [][]string 存内存,千万行 CSV 直接触发 OOM。真实导出必须边读边写、分批 flush。
- 用
csv.NewWriter(bufio.NewWriterSize(file, 1024*1024)),1MB 缓冲比默认快 3–5 倍 - 每写 1000–10000 行调一次
w.Flush(),避免 OS 缓冲区积压;如果写入 HTTP response,每次Flush()后还得检查w.Error() - 导出中文到 Excel 必须加 UTF-8 BOM:
file.Write([]byte("\xEF\xBB\xBF"))写在第一行之前,否则 Excel 默认按 ANSI 解析,显示为方块 - 字段含双引号?
csv.Writer不会自动转义,得提前把"替成"",否则 Excel 打开报错
导入时别依赖列顺序,必须解析 header 做字段映射
CSV 没 schema,靠列顺序映射结构体字段。一旦用户拖动 Excel 列或导出顺序变动,row[0] 就可能把邮箱塞进昵称字段。
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
立即学习“go语言免费学习笔记(深入)”;
- 先读第一行:
header, _ := r.Read(),拿到字段名切片 - 构建
map[string]int映射关键字段(如"email"→ 列索引),缺失则跳过该行并记日志 - 后续每行用
row[colIndex["email"]]取值,不硬编码下标 - 用
strconv.Atoi转数字前,先strings.TrimSpace,空字符串会直接 panic
数据库导出千万行别用 db.Query(),要流式分页 + 边读边写
db.Query() 默认吃光所有结果再返回,几百万行拉下来 RSS 突增几十 GB,最后 runtime: out of memory。
- MySQL 8.0+ 推荐
SELECT * FROM users LIMIT ?, ?分页,配合context.WithTimeout防卡死 - PostgreSQL 可用游标:
DECLARE csv_cursor CURSOR FOR SELECT ...,再FETCH 10000 FROM csv_cursor循环 - 千万别并发写同一
*os.File——磁盘 I/O 是物理串行的,goroutine 越多竞争越激烈,还可能破坏行边界 - 字段含时间?
csv.Writer不做任何转换,必须手动t.Format("2006-01-02");空值建议统一转为空字符串,避免写入<nil>
真正麻烦的不是“怎么写 CSV”,而是每个环节都默认不考虑中文环境、不修脏数据、不帮你分页——BOM、header 映射、缓冲区、flush 时机、空值和时间格式,漏掉任何一个,线上就会收到“导出的文件打不开”的截图。

















