直接存超大文本或二进制字段到GORM模型不加约束会导致写入膨胀、查询变慢甚至静默截断;[]byte字段默认被JSON编码为base64,体积膨胀33%且丧失原生BLOB索引能力,须用gorm:"type:bytes;serializer:gob"强制二进制直写。

直接存超大文本或二进制字段到 GORM 模型里,不加约束和序列化控制,大概率导致数据库写入膨胀、查询变慢、甚至静默截断 —— 不是 ORM 有问题,是你没告诉它“该怎么存”。
为什么 []byte 字段默认会变成 base64 膨胀?
GORM 对未显式声明序列化方式的 []byte 字段,会 fallback 到 JSON 编码:把原始字节转成 base64 字符串。这带来两个硬伤:
- 体积膨胀约 33%,100MB 文件实际存进数据库的是 133MB 的字符串
- 数据库无法按原生 BLOB 类型索引或高效扫描,
WHERE data LIKE '%xxx%'这类操作彻底失效 - PostgreSQL 的
BYTEA、MySQL 的MEDIUMBLOB都被绕过了,实际走的是TEXT或VARCHAR
必须用 gorm:"type:bytes;serializer:gob" 强制走 Go 原生二进制序列化,才能保持字节原样写入。
r.ParseMultipartForm 没调,r.FormFile 就一定返回 nil
multipart 解析是懒加载的,r.ParseForm() 完全无效;不调 r.ParseMultipartForm(10 (注意参数是内存缓冲上限,不是总大小),<code>r.MultipartForm 就是空的,r.FormFile 必然报 http: no such file。
- 设为
0会 fallback 到默认 32MB,语义模糊,别这么干 - 多文件要用
r.MultipartForm.File["files"]切片,r.FormFile只取第一个 - 解析后务必检查
len(files) > 0,否则range直接跳过无提示
大字段写入前必须流式直写,禁用 io.ReadAll
上传 200MB 视频时若用 io.ReadAll(r.Body) 或 bytes.Buffer 缓存,服务几乎必然 OOM,日志里出现 runtime: out of memory。
- 正确路径:从
hdr.Open()拿io.ReadCloser,用io.Copy(dst, src)直写磁盘或对象存储 Writer - 配合
http.MaxBytesReader(w, r.Body, 500 限流,防恶意请求放大攻击 - 需校验 SHA256?用
io.TeeReader(limitReader, hash),边读边算,避免二次遍历 - 写入前必须用
filepath.Join(uploadDir, safeFilename)拼路径,并清理../、强制重命名(如加 UUID 前缀)
字段类型必须对齐底层数据库,否则静默截断
SQLite 不写 gorm:"type:BLOB" 默认当 TEXT 处理;MySQL 超 64KB 不设 MEDIUMBLOB 或 LONGBLOB,就会静默截断 —— 没报错,但数据丢了。
- PostgreSQL/MySQL:统一用
gorm:"type:bytes;serializer:gob" - SQLite:必须显式写
gorm:"type:BLOB" - 字段长度预估超 64KB,MySQL 必须设为
MEDIUMBLOB(最大 16MB)或LONGBLOB(最大 4GB) - CGO 禁用后,若用了依赖 libzstd 的压缩中间件,相关功能会直接失效,编译可能报
undefined reference to 'ZSTD_compress'
最易被忽略的一点:GORM 的字段标签必须用反引号 ` 包裹,写成双引号或漏空格,反射就读不到,type:bytes 就等于没写。



















