go里存数据无统一方法,须按场景选工具链:结构化数据用database/sql+驱动并参数化查询;对象/日志抽象存储接口分环境实现;密码必须bcrypt哈希;配置用viper;会话用redis;切换存储介质的时机比语法更重要。

直接说结论:Go 里存数据,没有统一“存储方法”,只有按场景选对工具链。硬套一个方案(比如全用 JSON 文件或全上 MySQL)反而容易在并发、安全、扩展性上翻车。
存结构化业务数据,优先用 database/sql + 驱动
database/sql + 驱动不是“能不能连数据库”,而是“怎么连得稳、查得准、防得住”。
-
database/sql是标准接口,但必须配具体驱动(如github.com/go-sql-driver/mysql或github.com/lib/pq) - 连接字符串里密码别硬编码,用环境变量或 secret manager 注入
- 所有 SQL 查询必须用参数化:
db.Query("SELECT * FROM users WHERE id = ?", id),绝不用fmt.Sprintf拼接 -
sql.Open()只是初始化,不校验连接;真正检测要用db.Ping() - 长连接要设
SetMaxOpenConns和SetConnMaxLifetime,否则连接池耗尽或 stale connection 报错
存对象/附件/日志,别只盯着本地磁盘
os.WriteFile 或 ioutil.WriteFile(已弃用)适合单机调试,但一上生产就暴露问题:
- 多实例部署时,文件写在哪?同步怎么搞?
- 文件名含路径遍历字符(如
../../etc/passwd)没过滤,直接被覆盖 - 大文件上传没超时控制,goroutine 卡死不动
更稳妥的做法是抽象一层存储接口,比如:
- 本地开发用
os实现 - 测试用内存版(
bytes.Buffer+ map) - 生产切到 OSS/S3,用官方 SDK(如
github.com/aliyun/aliyun-oss-go-sdk/oss),注意三点:-
endpoint必须是纯 Region 地址(如"@#@#@#@#@#@#@#@#@#@0"),不能带 bucket 名 - AKSK 必须来自 RAM 子账号,权限最小化(只给
oss:PutObject、oss:GetObject等) - 所有上传操作显式加
oss.Timeout(60*1000)
-
存密码、token、敏感字段,禁止明文或快哈希
MD5、SHA256、甚至加盐 SHA256 都不行——GPU 暴力几小时就破。
- 用
golang.org/x/crypto/bcrypt,成本因子至少设为12 - 哈希结果直接存字符串(形如
"$2a$12$..."),别拆 salt 和 hash 分开存 - 验证时只调
bcrypt.CompareHashAndPassword,不要自己解析哈希结构 - 如果字段要加密(不只是哈希),别手写 AES-GCM,用
golang.org/x/crypto/nacl/secretbox或封装好的库,密钥必须由 KMS 管理
存配置、临时状态、轻量元数据,慎用纯内存
map[string]interface{} 或 sync.Map 看似简单,但容易忽略:
- 进程重启后全丢,无法跨实例共享
- 并发读写没加锁,map panic(
fatal error: concurrent map writes) - 内存泄漏难排查(比如缓存 key 永远不淘汰)
更可控的方式:
- 配置类数据用
github.com/spf13/viper,支持 YAML/TOML/环境变量多源加载 - 会话或临时 token 用 Redis,配合
SET key value EX 3600 NX保证原子写入 - 元数据类小表(如用户偏好)可走 SQLite,用
github.com/mattn/go-sqlite3,单文件、零依赖、ACID 保障
真正的难点不在“怎么存”,而在于什么时候该换存储介质——比如用户上传的头像,初期存在本地,用户量上来后必须切到对象存储;又比如订单状态变更,从内存 map 切到 Redis Stream 或 WAL 日志,才能支撑幂等和回溯。这些切换点,比语法细节重要得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











