← 返回博客/BUILD WITH AI

何时打破数据库规则:JSONB、计数器与分

何时打破数据库规则:经得起推敲的三个例外——真正灵活数据用JSONB、反规范化的计数器,以及把金钱存成整数分。每个例外都要有书面理由,其余一律保持结构化。

分类Build with AI
阅读时长1 分钟
已发布2026年10月8日
作者Luis Goncalves
何时打破数据库规则:JSONB、计数器与分

何时打破数据库规则,意味着你要有一个具体的、有文档记录的理由——而在实践中,只有三个理由经得起推敲。真正灵活的数据可以放进JSONB,而不是僵硬的表。实时计算代价过高的计数器可以存储起来。而金钱永远以整数“分”来存储。其余一切都保持严格的结构化。

我用了大量篇幅鼓吹结构:每个事实只放在一处,一切通过引用相连,不走捷径。这条规则——数据库规范化详解中讲过的90%以上的纪律——造就了一个数百张表保持同步、零重复数据的平台。本文讲的是另一面:那10%,打破规则不是捷径,而是正确的工程决策。

何时打破数据库规则:先有规则,再有例外

首先是框架。在我的平台里,大约95%的数据是关系型的——规范的表、规范的引用、规范的结构。例外就是例外,每一个都有书面理由。这个顺序很重要,因为AI会不断向你提出打破规则,而“这样更简单”永远不是理由。危险不在于例外本身,而在于没有记录的例外——之后没人能把它与错误区分开来。

说完这些:下面是我有意使用的三种规则打破。

例外一:真正灵活的数据用JSONB

PostgreSQL——大多数现代SaaS产品底层的数据库引擎——有一项超能力,叫JSONB。它让一个列容纳整份自由格式的文档:任意字段、任意形状,每行都可以不同。它是结构化世界的逃生舱。

把你的数据库想成一个带标签抽屉的文件柜。JSONB就是标着“杂项”的那个抽屉——杂项抽屉确实有用,只要它永远不会变成你存放护照的地方。合理的内容是:你无法事先知道形状的东西。如果你的用户要构建自定义表单,每份提交的字段都不同——你无法为尚不存在的字段预先设计抽屉。用户自定义的字段、每个用户的偏好和设置、来自其他公司系统的数据载荷、零散的元数据,以及审计日志里的前后快照,都是如此。

我遵循的一句话规则:

如果你需要把它与其他数据关联、频繁查询它,或强制维护关系——就用关系型。如果它是灵活的、用户定义的,或很少被查询——JSONB就可以。

JSONB适用于JSONB是错误的做法,当用于
表单回复(每个表单都不同)核心业务实体:用户、组织、项目
用户自定义字段任何你不断查询或筛选的内容
偏好与配置与其他记录有关系的数据
来自外部系统的传入数据载荷其正确性应由结构本身来保证的数据
审计日志的前后快照任何用于报表和分析的内容

JSONB安全的标志是:数据归属于单独一行,且从不脱离该行被单独查询。在这种情况下,结构本来要防止的那些更新问题根本不会发生。它是错误做法的标志是:你发现自己想要跨JSONB提问——“所有标记为紧急的任务”“这位客户的所有订单”。那些是关系,而关系属于表。

12 周企业职业生涯从来都不是计划申请加入 Solopreneur Accelerator。立即申请 →

例外二:无法承担实时计算的计数器

严格的结构说:永远不要存储你可以计算出来的东西。频道的成员数、商品的平均评分、职位的申请数量——这些都可以通过按需统计底层记录来计算,而在小规模时,这正是你应该做的。

然后规模来了。一个频道里有10万条帖子的社区,无法在每次渲染页面时重新统计。一个有50万条商品的市场,无法在每个搜索结果里重新计算平均评分。一个公司会收到5万份申请的招聘网站,无法在每次查看主页时实时统计。到这时,你就把这个数字存起来——一个预先计算好的计数器列——读取它不花任何代价。

让这不至于腐烂的纪律是:每个存储的计数器都需要一个维护机制——一个在底层数据变化的瞬间更新它的数据库触发器或函数。没有这个机制的计数器不是优化,而是一个等着被人相信的谎言。在我的数据库结构里,每一个计数器列都有记录在案的机制来保持同步,设计笔记会注明是哪一个、何时触发。

值得借鉴的一个细化:浏览量是例外中的例外。它们是异步更新的——页面加载后由页面触发——而不是通过触发器,所以统计浏览量永远不会拖慢页面,可以过滤掉机器人,重复浏览也可以去重。精确的计数器(成员、投票、申请)使用触发器;虚荣型计数器则采用宽松处理。

例外三:金钱永远是整数“分”

这一条与其说是“打破规则”,不如说是拒绝一个看似自然、实则暗藏危险的默认做法。把12.99欧元存成小数12.99,就等于把计算机的浮点运算请进了你的财务——而浮点运算会产生微小的舍入误差,在图表里只是难看,在发票上却无法接受。

解法:把每一笔金额都存成整数的“分”。12.99欧元存为1299。整数永远没有舍入误差。Stripe的整套支付系统都使用这一约定——以最小货币单位计金额——所以你的数据库与支付服务商天然匹配。除以100只在最后一刻、在显示层进行,在那里出错的代价只是一个外观上的bug,而不是金钱。

在我的数据库结构里,这一点是绝对的:每一个价格、费用、小费和总额都是整数分。金钱永远、在任何地方都不使用小数。

元规则:例外要写下来

注意这三个例外的共同点。每一个都有狭窄的触发条件(无法预知的形状;实测的实时计算成本;金钱的本质)。每一个都有补偿机制(JOIN测试;同步触发器;显示层转换)。而且每一个都有文档记录——我的平台维护着一份设计决策文件,列出每一处有意的偏离,正是为了让未来的审计(无论是人还是AI)能分辨出审慎的工程与日积月累的草率。

这才是专业的规则打破与技术债的真正区别:一个带着书面理由,另一个带着耸肩。

本书The Solopreneur RevolutionLuis Gonçalves 讲述如何在不辞职的情况下用 AI 打造独立创业者事业。获取本书 →

常见问题

JSONB是不良实践吗?

不——被滥用的JSONB才是不良实践。对于真正灵活、归属于单行的数据,它是正确的工具,能避免无谓的复杂性。失败模式是用它来逃避数据库设计:因为设计规范的表感觉像是活儿,就把标签、负责人或关系塞进一个JSON块。应用JOIN测试,JSONB就仍是工具而不是陷阱。

我怎么知道什么时候该存储计数器?

当实时统计造成可测量的伤害时——一个每次渲染都要重新统计大量记录的页面——而不是更早。先从按需计算开始;只有当规模迫使你这么做时才存储计数器,而且绝不能没有让它保持准确的触发器或函数。过早的计数器是无谓赚来的复杂性。

为什么不直接用小数列存金钱?

因为整数分让一整类财务bug变得不可能,而不是不太可能;也因为你的支付服务商本来就以最小货币单位思考。金钱有两种表示——Stripe那边是分,你的数据库里是小数——意味着到处都要转换,而每一次转换都是在某人的发票上出错的机会。

如果我一切都用JSONB会怎样?

你会得到一个无法回答问题的数据库。没有关系,没有完整性约束,没有高效筛选,报表痛苦不堪——你不过是用额外的步骤重建了一堆文档。为了你并不需要的灵活性,牺牲了你的生意赖以运转的可查询性。把结构留给那95%,把JSONB用在形状确实无法预知的地方。

你到底被困得有多深?

完成职场窒息指数——这项免费测评告诉你是否已准备好成为独立创业者。

开始测评 →