离线数据批量入库需流式解析、分批事务、错行隔离;用c.request.formfile获取文件,bufio.newreader跳bom,pgx.copyfrom或mysql多值insert分批写入,每批配独立超时context,失败行单独记录。

离线数据批量入库不是“读完再插”,而是“边读边控、分批事务、错行隔离”。用 c.Request.FormFile 拿到文件后,必须立刻流式解析(别 ioutil.ReadFile),每批 500–2000 行走一个显式事务,出错只跳过当前行,不中断整个流程。
怎么安全读取上传的 CSV/Excel 文件
别依赖 c.PostForm 拿文件名或参数——它在 multipart 场景下经常为空,因为 Gin 的 formCache 只在成功调用 ParseMultipartForm 后才填充。直接用 c.Request.FormFile("file") 是唯一可靠起点。
- 拿到的
file是io.ReadCloser,必须defer file.Close(),否则临时文件句柄泄漏 - 对
header.Filename做基础校验:过滤../、空扩展名、非预期类型(如.exe) - CSV 要检查 BOM:用
bufio.NewReader(file)先Peek(3),若匹配[]byte{0xEF, 0xBB, 0xBF}就Discard(3) - Excel 用
excelize.OpenReader(file),别试图用io.ReadAll把整个 XLSX 加载进内存
为什么不能用 db.Exec 循环插入每一行
这不是性能问题,是稳定性崩塌点:万行数据意味着万次 round-trip、万次 WAL 写入、万次连接池争抢。MySQL 默认 max_allowed_packet=4MB,PostgreSQL 有 statement_timeout 和参数数量硬限制,第 1892 行就可能触发 context.DeadlineExceeded 或 ERROR: bind message has 2000 parameters but prepared statement "xxx" requires 1。
- 每条
db.Exec都是独立事务,没显式tx := db.Begin()就等于让数据库反复刷磁盘 - 错误时无法原子回滚:前 3000 行成功入库,后 7000 行因某列超长失败,你根本不知道哪些丢了
- Go 默认连接池大小常为 2–10,高并发插入会卡死在
sql.DB.GetConn上
PostgreSQL 用 pgx.CopyFrom,MySQL 拼多值 INSERT
二者路径完全不同,混用会踩坑。PostgreSQL 必须用 pgx.Conn(不是 *sql.DB),MySQL 则必须手动拼 INSERT INTO t VALUES (),(),() 并安全转义。
- PostgreSQL:
conn.CopyFrom(ctx, pgx.Identifier{"users"}, []string{"id","name"}, dataRows),其中dataRows是[][]interface{},每行字段顺序和类型必须与表严格一致(int64不能传int) - MySQL:单条语句最多 1000 行,用
mysql.Escape()(不是sql.EscapeString)转义字符串,数值类型用strconv.FormatInt显式转换,避免fmt.Sprintf溢出 - 两者都需分批:PostgreSQL 推荐 1000–5000 行/批;MySQL 若字段含
TEXT或索引多,建议降到 200–500 行/批
出错时怎么记录失败行并继续
pgx.CopyFrom 错误只报 copy failed at row N,不告诉你哪一列、什么类型错;MySQL 拼 SQL 出错更模糊。靠 panic 或全量回滚是生产事故温床。
- 每批执行前生成唯一批次 ID(如
uuid.New().String()),出错时把原始行内容 + 错误信息写入本地日志文件(路径用os.MkdirAll确保存在) - 别在循环里反复
log.Printf,用bufio.NewWriter批量刷盘,避免 IO 卡住主流程 - 对 CSV 解析失败(如字段数不对)、类型断言失败(如
row[2].(string)panic)、数据库约束冲突(unique_violation)要分类处理:前两类记日志跳过,后者可重试或人工介入 - 最后返回 JSON 包含
"success_count": 9872, "failed_count": 128, "failed_batch_ids": ["abc123", "def456"]
最易被忽略的是事务粒度与上下文超时的绑定:每个批次必须配独立 context.WithTimeout(ctx, 30*time.Second),而不是复用 handler 的全局 context。否则一个慢批会拖垮后续所有上传请求。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











