goroutine并非越多越好,应使用固定数量worker池(如NumCPU()*2)处理任务,避免调度器过载;批量数据库写入需用原生COPY协议或拼接INSERT;大文件处理须用bufio.NewReaderSize而非Scanner;排序应根据数据量和GC开销权衡并发,并控制内存边界。

goroutine不是越多越好,worker池得卡死数量
直接对每条数据起一个 goroutine,在百万级数据下不是快,是崩——调度器被压垮、内存暴涨、GC 频繁 STW。Go 运行时默认 GOMAXPROCS 等于 CPU 核数,但 10 万 goroutine 同时阻塞在 I/O 或简单计算上,大部分时间都在抢调度权。
- 用固定 worker 池消费任务队列,数量设为
runtime.NumCPU() * 2,别硬塞runtime.GOMAXPROCS(100) - 任务粒度要实:单个 worker 处理 100–1000 条数据,减少 channel 通信频次
- 别用无缓冲
chan串接高吞吐流水线——锁和内存屏障开销会在十万级消息/秒时明显抬高延迟 - 优先用切片分片 +
sync.WaitGroup,channel 只用于退出信号或错误通知
批量写入数据库必须绕过 Exec 循环
用 db.Exec 逐行插入万级数据,大概率在第 832 行就挂:连接池耗尽、WAL 写满、主从延迟飙升。这不是代码逻辑错,是协议层瓶颈。
- PostgreSQL 场景:必须用
pgx.CopyFrom(不是database/sql+pq),走原生 COPY 协议,字段顺序和类型必须严格匹配 - MySQL 场景:底线是拼多值
INSERT,上限是LOAD DATA LOCAL INFILE(需服务端开启local_infile=ON) - 通用分批事务:批次大小不能拍脑袋定 1000,要看字段是否含
TEXT、索引数量;超时必须单独设context.WithTimeout,别复用全局 context - GORM 的
CreateInBatches不是银弹——底层仍是Exec循环,对 PostgreSQL 比pgx.CopyFrom慢 5–10 倍
大文件读写不能依赖 bufio.Scanner
bufio.Scanner 是为“快速逐行消费”设计的,不是为事务处理。它不暴露当前偏移、无法 Seek 回退、单行超长直接 panic,且 scanner.Bytes() 返回的是底层缓冲切片,存进 map 就导致整块缓冲被 GC 持有。
- 事务型文件处理(如日志解析+落库)必须用
bufio.NewReaderSize+file.Seek - 缓冲区大小设为
64 * 1024(64KB),页对齐且吞吐稳定 - 不用
ReadString('\n'),改用ReadBytes('\n')或手动Read+ 扫描,防止单行撑爆缓冲 - 每次读完立即处理
buf[:n],处理完立刻buf = buf[:0]清空头,帮 GC 识别可复用空间
排序与归并不等于并发,关键在“不越界”
1000 万条数据要不要并发排序?不是看 CPU 闲不闲,是看单次耗时是否 > 200ms。10 万条以下并发反而慢 15%,调度开销盖过收益。真正卡住的常是 GC —— 结构体含指针(如 []byte、map[string]int)会让 GC 扫描开销叠加进排序耗时。
立即学习“go语言免费学习笔记(深入)”;
- 纯数值排序直接用
sort.Ints/sort.Float64s,它们走汇编优化路径,无抽象开销 - 结构体排序别用
sort.Slice闭包——接口动态调度 + CPU 缓存不友好访问会拖慢;改用sort.Sort实现sort.Interface - 两个 50GB 有序文件合并?别加载进内存。用两个
*csv.Reader维护当前行,比较键后写出,一个读完后直接io.Copy剩余内容 - 离线批处理可临时
debug.SetGCPercent(-1),但记得恢复——线上环境绝对禁止
最容易被忽略的是:哪怕单 goroutine,只要不 malloc 几 GB,就能稳跑完。复杂点不在并发模型,而在内存边界控制和 GC 行为预判。


















