分批次大批量导入在Fiber中需避免内存暴涨、连接超时与GC压力,核心是流式处理:禁用ctx.Body(),改用ctx.MultipartForm()获取FileHeader后Open()流式读取;CSV/JSONL按行解析不全加载;数据库批量写入并控制并发;响应返回202并用SSE推送进度。

分批次大批量导入在 Fiber 中不是“加个循环就完事”,核心矛盾是内存暴涨、连接超时、GC 压力突增——直接用 ctx.Body() 读完整体文件或一次解析全部 JSON 行,50MB 文件就能让单请求卡死。
别用 ctx.Body() 读大文件,改用 ctx.MultipartForm()
上传大文件时,ctx.Body() 会把整个 multipart body 拷贝进内存;而 ctx.MultipartForm() 返回的是 *multipart.Form,其 File 字段是 map[string][]*multipart.FileHeader,只存元信息,真正读取靠 fileHeader.Open() 拿到 io.ReadCloser 流式处理。
- 必须检查
err:上传中断或格式错误时,ctx.MultipartForm()可能返回nil, err,不能直接解引用 - 文件名和大小要从
fileHeader.Filename和fileHeader.Size获取,别信前端传的 hidden 字段 - 若允许多文件,遍历
form.File["files"],每个都Open()后立即处理,用完立刻Close()
按行/按块流式解析 CSV 或 JSONL,不全加载
常见错误是把整个 CSV 或 JSONL 文件读成 []byte 再 strings.Split() 或 json.Unmarshal() —— 这等于把所有数据塞进内存。正确做法是边读边解析:
- 对 CSV:用
csv.NewReader(fileReader)+reader.Read()循环,每行解析后立即入库或暂存批量缓冲区(如[]map[string]string,长度达 1000 就INSERT) - 对 JSONL(每行一个 JSON 对象):用
bufio.Scanner逐行扫描,每行用json.Unmarshal(line, &item)解析,避免json.Decoder的额外 buffer 开销 - 缓冲区清空后记得
reset切片底层数组(如batch = batch[:0]),防止内存被旧引用持有
数据库写入必须批量 + 控制并发,别单条 insert
Fiber 本身不处理 DB 批量逻辑,但错用 ORM 或原生 SQL 会导致 QPS 断崖下跌。例如用 GORM 的 Create(&item) 循环 10 万次,等于发 10 万次 round-trip;而 CreateInBatches(items, 1000) 只发 100 次。
- PostgreSQL 推荐用
pgx.Batch或INSERT ... VALUES (...), (...), ...拼接(注意参数上限,通常 ≤ 65535 个占位符) - MySQL 注意
max_allowed_packet,批量条数建议 ≤ 1000,超了就拆批 - 用
sync.WaitGroup控制并发批次数(如最多同时跑 3 个INSERT),避免 DB 连接池打满或锁表 - 每批执行后检查
err,失败则记录错误行号和原始数据,不要整个事务回滚丢弃全部进度
响应必须分阶段反馈,别等最后才 return
用户上传 200MB 文件后干等 90 秒没响应,大概率点刷新重试——这不是体验问题,是协议层超时(Nginx 默认 60s,浏览器也常设限)。必须主动推进状态:
- 上传完成即返回
202 Accepted+{"task_id": "import_abc123"},后端异步处理 - 另开 endpoint 如
GET /import/status/:task_id返回当前进度(已处理行数 / 总行数)、错误摘要、预计剩余时间 - 前端用
setTimeout轮询,或更优方案是用 SSE(ctx.Set("Content-Type", "text/event-stream"))实时推送进度 - 任务完成后清理临时文件和缓存,别让
/tmp堆满
最易被忽略的是:Fiber 的 ctx.Locals 不跨 goroutine 生效,如果你在异步 goroutine 里想读取上传时存的用户 ID,必须显式传参或用 ctx.Context().Value();否则拿到的是 nil,导致权限校验失败或日志丢失上下文。


















