导出接口须流式写入避免OOM:设Content-Disposition/Type、禁用自动压缩与Header写入;分页查库+bufio.Writer逐批flush;禁用Excel库,优先CSV/JSONL;监听ctx.Request().Context().Done()响应前端取消。

导出接口不能直接用 ctx.JSON 或 ctx.Write
大批量数据(比如上万行 CSV/Excel)一旦走 ctx.JSON 或 ctx.Write,容易触发内存暴涨甚至 OOM。Iris 默认把整个响应体缓冲在内存里,等 handler 返回才 flush;导出场景必须绕过这个默认行为,改用流式写入。
正确做法是:设置响应头 + 直接写入 ctx.ResponseWriter().Writer(),并配合 bufio.Writer 提升吞吐。关键点有三个:Content-Disposition 告诉浏览器下载文件名、Content-Type 匹配格式、禁用 Iris 自动压缩(避免流式写入被截断)。
ctx.Header("Content-Disposition", "attachment; filename=users_export.csv")ctx.Header("Content-Type", "text/csv; charset=utf-8")-
ctx.SetStatusCode(200)(显式设状态码,避免 Iris 后续覆盖) - 调用
ctx.ResponseWriter().DisableHeaderWrite(true)禁用自动 Header 写入(防止和你手动写的冲突)
分页查库 + 流式写入,别 load 全量进内存
常见错误是先 db.Find(&allUsers) 把几万条记录全塞进 slice,再遍历写 CSV —— 这会吃光 Go runtime 的堆内存。真正可落地的做法是用数据库游标或 LIMIT/OFFSET 分批拉取,每批 1000–5000 条,写完一批就 flush 一次。
以 PostgreSQL 为例,用 rows, err := db.Query("SELECT ... ORDER BY id OFFSET ? LIMIT ?", offset, batchSize) 配合 sql.Rows.Scan 逐行读;MySQL 可用类似方式,注意避免 OFFSET 深翻性能退化,必要时改用基于主键的游标(如 WHERE id > ? ORDER BY id LIMIT ?)。
- 每次从
rows读一行,立刻csvWriter.Write(record) - 每写满 100 行调一次
writer.Flush()(bufio.Writer默认 4KB 缓冲,但主动 flush 更可控) - 循环中检查
ctx.IsStopped(),前端取消请求时及时退出
别用第三方 Excel 库做服务端导出
像 excelize 或 tealeg/xlsx 这类库,生成 .xlsx 文件需要构建完整 ZIP 结构,内存占用是 CSV 的 5–10 倍,且 CPU 开销大。生产环境导出万级数据,优先选 CSV/TSV/JSONL(换行分隔 JSON),它们天然支持流式生成,浏览器也能直接打开或用 Excel 导入。
如果业务强要求 .xlsx,必须加超时和资源限制:用 context.WithTimeout(ctx.Context(), 30*time.Second) 包裹整个导出逻辑,并在 handler 开头检查 ctx.GetHeader("User-Agent") 是否为可信客户端(防恶意刷导出)。
- CSV 字段含逗号或换行?用双引号包裹字段,
csvWriter.Comma = ','无需额外 escape - 中文乱码?确保
Content-Type带charset=utf-8,且写入前不经过任何非 UTF-8 编码转换 - 导出耗时长?加
ctx.Timeout(120 * time.Second)防止连接空闲断开
前端取消请求时后端要真停止,不是假装停止
用户点「取消下载」或关掉标签页,浏览器会断开 TCP 连接,但 Go HTTP server 不会立刻感知。Iris 的 ctx.IsStopped() 依赖底层 http.CloseNotifier(Go 1.8+ 已弃用),实际应监听 ctx.Request().Context().Done()。
所以导出循环里不能只靠 if ctx.IsStopped() { break },必须结合 select:
for rows.Next() {
select {
case <-ctx.Request().Context().Done():
return // 真正退出
default:
// 正常读取写入
}
}否则会出现:前端已放弃,后端还在默默查库、写文件、占内存,直到超时或 panic。
导出最麻烦的从来不是格式或库,而是“怎么让大数据不卡死服务”。流式写入、分批查库、及时响应中断 —— 这三件事漏掉任何一个,都可能让一个导出接口变成线上事故入口。


















