2.2 数据库选型:JSON 文档还是关系型
环境跑通后,第一场真正的技术讨论来了:文章数据存哪。云开发默认提供的是文档型数据库(类似 MongoDB 的 JSON 文档模型),但腾讯云生态里关系型数据库也很成熟。我们俩一个写了多年 MySQL,一个只会 JSON,吵了半个晚上。这一节把当时的对比过程和最终决定完整记录下来,比教科书上的概念对照更贴近真实决策。
场景:一篇文章长什么样
先看数据本身的形态。一篇图文文章,剥到最薄是这样:
{
"title": "深秋的巷口",
"cover": "posts/2026/10/cover-abc.jpg",
"body": "这里是正文的 Markdown 源码,长短随意",
"tags": ["摄影", "随笔"],
"authorId": "u_1001",
"createdAt": 1761292800000,
"likes": 12
}
关键词是不规则:正文长短不一,标签数量从零到十几个,以后还可能加封面图列表、扩展字段、审核状态。这种读到再写、随时长 schema 的形态,是文档型数据库的主场。
再看查询形态。图文应用的访问模式高度收敛:列表分页查、按 ID 查详情、按标签筛、按时间排序。没有复杂的跨表 JOIN,没有事务一致性要求,最多就是点赞数和文章详情的最终一致。这个判断很关键,选型不是选最强的,是选最贴访问模式的。
把访问模式和存储特性对号入座,结论其实已经写在里面了:
| 访问模式 | 频次 | 文档型支持方式 |
|---|---|---|
| 首页列表分页 | 最高 | where 加 orderBy 走索引 |
| 文章详情 | 高 | 按 _id 直查 |
| 标签筛选页 | 中 | 数组包含查询 |
| 个人主页 | 中 | authorId 条件查询 |
| 作者信息展示 | 高 | 列表里冗余字段,免关联 |
| 点赞计数更新 | 中 | 原子自增操作 |
六种访问模式里没有一种需要多表关联,全部是单集合条件查询。如果哪天报表需求多到要跑复杂统计,文档型数据库的聚合管道也能做,这个在列表获取一节里展示了我们写的聚合查询。访问模式决定存储选型,而不是反过来,这个顺序别搞反。
方案对比:文档型 vs 关系型
把两种方案在我们这个场景下的差异摊开:
| 对比项 | JSON 文档型 | 关系型(MySQL 等) |
|---|---|---|
| 数据形态 | 灵活 schema,字段随加随有 | 结构固定,改表要迁移 |
| 存储单位 | 集合(Collection)存文档(Document) | 表(Table)存行(Row) |
| 嵌套数据 | 天然支持,标签数组直接存 | 需要拆子表再 JOIN |
| 查询能力 | 灵活过滤、聚合,无跨表 JOIN | 完整 SQL,事务和外键 |
| 与云函数协作 | 同一环境内 SDK 直连 | 另开实例,网络和费用都独立 |
| 成本 | 跟环境套餐一起,免费额度内 | 单独实例,最低配置也是固定开销 |
| 学习成本 | 会 JSON 就会写 | 要懂建模和 SQL 优化 |
最后两行是决策的落点。独立开发者最稀缺的是 注意力和预算:文档型数据库在云开发环境里是自带的,和云函数、存储同一个生态,SDK 直接读写,没有连接池、没有实例续费、没有慢查询告警需要兜底。
关系型当然有它不可替代的场景:订单系统要事务,财务对账要强一致,复杂报表要多表关联。可我们的图文项目一条都不沾。选型讨论里最容易犯的错,就是为永远不会出现的需求过度设计,这个反面例子在腾讯云实践里屡见不鲜。
顺带记录一个小插曲:讨论到一半我们差点因为 用户表加关联订单 的假想需求推翻文档型方案,冷静下来一想,图文项目连付费功能都不在规划里。把假想需求写在白板上逐条划掉之后,文档型方案反而更清晰了。远了想不如近了做,MVP 阶段存储选型的决策窗口就该花在真实访问模式上。
另外还有一层现实考虑:团队就两人,前端只熟 JSON,让他去学 MySQL 建模至少要一周,而文档型他看一眼就会写。上线速度面前,这一周比任何技术指标都值钱。
决定:文档型,但加上纪律
最终我们选了文档型,但加了几条自定的纪律,防止灵活变成混乱。这几条纪律后来在数据表设计一节里落到了字段层面:
- 每个集合先写字段约定再写代码,字段名、类型、是否必填形成文档,放项目 README 里
- 时间一律存毫秒时间戳(int 类型),杜绝字符串日期格式不统一
- 需要查询的字段提前建索引,写代码前先想查询条件,而不是上线后再补
- 一对多关系用引用 ID,不用大数组无限嵌套
文档型数据库里嵌套自由不等于嵌套合理。 tags 这种短数组可以直接存,但评论如果也塞进文章文档里,文档体积会随时间膨胀,列表查询时会把无用数据一起拉出来。我们的评论是独立集合,用 postId 做引用。这个分界线是:会被列表整体读取的数据放在主文档,独立增长的数据拆出去。
集合规划最终落地成这样一张图,项目里就按它来建:
| 集合 | 存什么 | 读写特点 |
|---|---|---|
| posts | 文章主数据 | 读多写少,列表走索引 |
| users | 用户信息 | 按登录态查,读为主 |
| comments | 评论记录 | 随文章增长,独立分页 |
| likes | 点赞流水 | 写频繁,原子计数配合 |
| counters | 计数器汇总 | 聚合结果缓存,降低扫描 |
前两个是 MVP 就有的,后三个是业务跑起来后逐步拆出来的。一开始只有 posts 和 users 完全没问题,集合随业务长,比一上来铺五个空集合务实得多。
实际操作上,云开发控制台的数据库面板可以直接建集合、贴 JSON 写记录,也能加索引,全程不用 SQL:
# 或者用 CLI 完成同样的事
tcb db create collection posts
# 写一条记录验证
tcb db add posts --data '{"title":"测试","authorId":"u_1001"}'
数据库的官方能力细节可以对照本书目录里数据库相关章节,那里有完整的增删改查实操。存储选型部分如果好奇对象存储的用法,可以看后面的图片直传一节,和数据库是同一套环境内的配合。
踩坑:选了文档型才踩的坑
坑一:把关系型思维带进来。 写第一版列表查询时,我习惯性地先查文章 ID 列表再循环逐条查详情,一页十篇文章就是十一次查询。后来用聚合(aggregate)一次查完,接口耗时从八百毫秒降到两位数。文档型不等于不需要优化,只是优化姿势不同。同样的错误还出现在分页上:传统 OFFSET 分页在文档型里要改用游标分页(拿上一页最后一条的 createdAt 作为下一页起点),深度翻页才不会越翻越慢。
坑二:字段名笔误造成沉默错。 有的地方写 createdAt,有的写成 created_at,文档型数据库不报错,查询时默默少半边数据。纪律文档加代码审查能挡住,ESLint 也可以加规则扫字段写法。上线后才发现时已经积了几千条脏数据,清洗花了一整晚。
坑三:索引在数据量上来后补,很疼。 列表按 createdAt 倒序排,早期几百条没感觉,破万之后分页明显变慢。临时加索引会触发全量扫描构建,线上正在写入时操作有风险。正确做法是上线前按查询条件预建索引,宁可先建后用,哪怕个别索引白建了,成本也可忽略。
坑四:免费额度按读写次数算。 有次写脚本批量修数据,一个循环里逐条 update,几万次调用把当天额度刷爆。批量操作用批量更新接口或聚合管道,几条命令解决,别按关系型逐行更新的思路来。
坑五:文档大小有上限。 有篇长文连图带字贴成一篇巨文,文档体积接近单条上限,写入偶发失败。长正文考虑拆分章节字段,或者正文里只存 Markdown 而把渲染产物单独缓存。图片路径本来就只占几十字节,真正占体积的是塞错位置的 base64。
坑六:集合命名漂移。 今天叫 posts 明天叫 articles,代码里两边都引用过,结果一半查询打空集合。集合命名同样写进约定文档,统一复数、统一小写、不用缩写,比事后改名一地鸡毛强。
小结
选型的核心就一句话:让数据形态贴合访问模式。图文应用是读多写少、形态不规则、弱一致需求的典型场景,与文档型数据库天然匹配,而且与云函数同环境免联调、费用并入套餐,综合得分远超关系型。纪律与索引是文档型方案能走远的两条腿。
你的场景如果反过来——强事务、多表关联、报表驱动——那就别勉强,云开发里混用关系型实例也是常见做法,只是辛苦和预算都要自己担。选型决策之外,更完整的项目级权衡可以回看第一章的传统 vs Serverless。
延伸阅读
- Python 云开发实战目录:全书结构,含数据库与云函数实操
- 腾讯云实践:其他云项目的数据库选型复盘
