go通过c.request.formfile安全读取上传csv:先peek检测并跳过bom,用csv.newreader设fieldsperrecord=-1容错解析,流式分批事务写入数据库,禁用db.exec循环插入,按postgresql(pgx.copyfrom)或mysql(多值insert)差异处理,每批500–2000行并隔离错误行。

直接用 c.Request.FormFile 拿文件,流式解析 + 分批事务写入,别走 db.Exec 循环插入——那是万行数据崩盘的起点。
怎么安全读取上传的 CSV 文件
别信 c.PostForm("file") 或 c.FormValue,multipart 场景下它们大概率为空;c.Request.FormFile("file") 是唯一可靠入口。拿到的 file 是 io.ReadCloser,必须 defer file.Close(),否则临时文件句柄泄漏。
- 先用
bufio.NewReader(file)Peek(3)检查 BOM:[]byte{0xEF, 0xBB, 0xBF}就Discard(3) -
header.Filename要校验:过滤../、空扩展名、非.csv后缀 - 用
csv.NewReader时设reader.FieldsPerRecord = -1允许可变列数,避免某行多一列就全盘失败
为什么不能用 db.Exec 逐行插入
这不是慢不慢的问题,是数据库连接池、WAL 日志、参数数量限制三重暴击:
Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。
- MySQL 默认
max_allowed_packet=4MB,1000 行含字符串就可能超限 - PostgreSQL 报错
bind message has 2000 parameters but prepared statement requires 1是常见现场 - 每条
db.Exec都是独立事务,没tx := db.Begin()就等于让磁盘反复刷 WAL - Go 默认
sql.DB连接池大小常为 2–10,并发批量导入时卡死在GetConn上
PostgreSQL 和 MySQL 的分批入库写法差异
二者底层机制不同,混用会踩坑;必须按数据库类型选路径:
- PostgreSQL:用
pgx.Conn.CopyFrom,不是*sql.DB——conn.CopyFrom(ctx, pgx.Identifier{"users"}, []string{"id","name"}, dataRows),其中dataRows是[][]interface{},字段顺序和类型(如int64不可传int)必须与表严格一致 - MySQL:拼多值
INSERT INTO t VALUES (),(),(),单条最多 1000 行;字符串用mysql.Escape()(不是sql.EscapeString),数值用strconv.FormatInt显式转,避免fmt.Sprintf溢出 - 无论哪种,每批控制在 500–2000 行,配独立
context.WithTimeout,失败只跳过当前批,不中断整个流程
最容易被忽略的是错行隔离——整批失败就丢掉全部?不行。得把解析失败的行、入库失败的行分别记录到日志或单独表里,否则用户根本不知道哪几行没进去,而你也没法补救。










