用database/sql查数据后写入文件需流式读取rows并逐行写入os.File,避免全量加载OOM;CSV须手动类型转换为[]string并处理nil,JSON不可直接marshal rows,须Scan后构造结构体或map再序列化。

用 database/sql 查完数据后怎么写进文件
查完数据不直接用,而是要落地成文件(比如 CSV、JSON 或纯文本),核心就两步:读出 rows,逐行扫并写入 *os.File。别想着“一键导出”,Go 没这种封装,得自己控制循环和编码逻辑。
常见错误是调用 rows.Scan() 前没检查 err,或者扫完没调 rows.Close(),导致连接泄漏;还有人把整张表 load 到内存再写,大表直接 OOM。
- 始终用
defer rows.Close()(在rows, err := db.Query(...)之后立刻加) - 用
for rows.Next()流式读,别用rows.SliceScan()或全量sqlx.StructScan - 写文件优先用
bufio.NewWriter包一层,避免每行都 syscall
导出 CSV:字段类型不一致怎么办
csv.Writer 只认 []string,但数据库列可能是 int64、time.Time、nil(sql.NullString 等)。不能直接传 interface{} 给 Write(),会 panic。
必须手动做类型转换和空值处理。比如 sql.NullInt64 要判断 .Valid,time.Time 要转成 .Format("2006-01-02")。
立即学习“go语言免费学习笔记(深入)”;
- 别用
fmt.Sprint(v)直接转——对nil的sql.NullString会输出{ false }这种垃圾字符串 - 用
switch v := col.(type)分支处理,重点覆盖nil、int、float64、bool、time.Time、string - CSV 中的双引号、换行符、逗号必须由
csv.Writer自动转义,别自己拼字符串
写 JSON 文件:为什么不能直接 json.Marshal(rows)
rows 是个迭代器,不是数据切片,json.Marshal() 完全不认识它。强行传进去只会得到 {} 或 panic。
正确做法是定义目标 struct(或 map[string]interface{}),每轮 rows.Next() 时 Scan() 进去,append 到 slice,最后再 json.MarshalIndent()。但注意:大结果集仍会吃光内存。
- 如果只是“导出调试”,用
[]map[string]interface{}最省事,配合rows.Columns()和rows.Scan()动态取值 - 如果要流式写 JSON 数组(不加载全量),得手写
{+ 循环写{"k":"v"}+ 末尾加},并处理逗号分隔逻辑 -
time.Time默认 JSON 输出是带时区的字符串,如需 ISO8601 简化格式,得注册自定义MarshalJSON方法
文件写入失败时,事务和连接状态怎么管
写文件失败(磁盘满、权限不足、路径不存在)不会自动 rollback 数据库事务。很多人以为 “查完就写,写崩了查的数据就白费”,其实查本身不改数据,但如果你是在事务里 SELECT FOR UPDATE 后写文件,卡住就等于锁一直占着。
- 文件操作前确保目录存在:
os.MkdirAll(filepath.Dir(filename), 0755) - 打开文件用
os.Create()或os.OpenFile(..., os.O_CREATE|os.O_WRONLY, 0644),别用os.Open()(只读) - 写完务必调
w.Flush()(bufio.Writer)和f.Close(),否则内容可能滞留在 buffer 里 - 错误处理要分层:DB error、scan error、file open error、write error —— 它们恢复方式完全不同
最易被忽略的是:用 log.Fatal() 之类退出程序时,defer 不会执行,rows 和 file 都没 close,下次跑可能连不上库或打不开文件。


















