database/sql.Query() 会全量加载结果集导致 OOM,必须用 Rows.Next() 流式处理;CSV 读取需 bufio.Reader 加大缓冲并手动跳 BOM;批量插入应复用 Prepare 语句分批执行。

database/sql.Query() 会吃光内存,必须改用 Rows.Next()
Go 默认的 db.Query() 会把整张表结果集缓存到内存,几百万行就直接触发 runtime: out of memory。这不是驱动问题,而是 API 语义本身决定的——Rows 对象在调用 Next() 前,底层已把全部数据拉完。
- PostgreSQL:用
DECLARE cursor_name CURSOR FOR SELECT ...+FETCH 10000 FROM cursor_name显式游标,避免全量加载 - MySQL 8.0+:用
SELECT * FROM t LIMIT ?, ?分页,配合context.WithTimeout防卡死 - 别信“加了
Rows.Close()就安全”——Close 不释放已缓存的数据,只释放连接 - 验证是否生效:观察进程 RSS 内存是否随行数线性增长;若稳定在几十 MB,说明流式成功
csv.NewReader 必须套 bufio.Reader,且 BufferSize 要设大
直接传 *os.File 给 csv.NewReader() 看似简单,但默认 4KB 缓冲在真实 CSV 场景下极易出错:引号包裹的换行符跨 buffer、syscall 频繁导致机械盘卡顿、BOM 处理失败。
- 用
bufio.NewReaderSize(f, 64*1024)替代裸文件,64KB 是多数 SSD/网络存储的甜点值 - 手动跳 BOM:
b := buf.Bytes(); if bytes.HasPrefix(b, []byte("\ufeff")) { b = b[3:] },再传给csv.NewReader(bytes.NewReader(b)) - 字段数不固定(比如备注列含逗号)?设
reader.FieldsPerRecord = -1,否则某行少一列就 panic - 别用
ReadAll()—— 它返回[][]string,嵌套切片开销比单层大 3 倍以上
批量插入不能拼 SQL 字符串,要用 Prepare + Exec
用 fmt.Sprintf("INSERT INTO t VALUES (%s)", strings.Join(values, ",")) 拼接千万行,不仅慢(字符串分配+GC 压力),还容易被注入或字段错位。真正高效的批量写入靠的是预编译 + 参数绑定。
从 CSV 数据集生成图数据库加载器和三元组映射。将表格 CSV 数据转换为图数据库可直接使用的节点、边和三元组。
- 提前
stmt, _ := db.Prepare("INSERT INTO t(col1,col2) VALUES(?,?)"),复用同一 stmt - 每 1000–10000 行做一次
stmt.Exec(args...),具体数值看数据库单次事务承受力(PostgreSQL 推荐 5000,MySQL 10000) - 字段含空值?用
sql.NullString等类型,别直接传nil或"",否则类型不匹配报错 - 时间字段别用
time.Now().Format("2006-01-02")—— 数据库驱动通常支持time.Time直传,格式化反而丢失时区
并发写数据库 ≠ 并发读 CSV,别乱开 goroutine
常见误区是“开 10 个 goroutine 同时往数据库 insert”,结果连接池打满、锁竞争加剧、事务冲突变多,吞吐反而下降 40%。
立即学习“go语言免费学习笔记(深入)”;
- CSV 读取必须单 goroutine 流式进行(保证顺序和内存可控)
- 可以开多个 goroutine 处理解析后的数据(如清洗、校验),但写入数据库仍应串行或通过带缓冲 channel(
ch := make(chan []interface{}, 1000))统一调度 - 别让多个 goroutine 共享同一个
*sql.Tx—— 它不是线程安全的,Exec调用可能 panic - 如果真要提速,优先压缩传输:用
gzip.NewWriter()包一层 http.ResponseWriter 或文件写入器,CPU 换 IO 减少 60%
实际跑通的关键点往往藏在细节里:BOM 是否 trim、缓冲区大小是否匹配存储介质、Prepare 是否复用、空值是否用 Null 类型封装。这些地方错一个,轻则导入中断,重则数据错位,而且错误日志里很难定位。

















