打开网易新闻 查看精彩图片

1986年的论文,2025年的MongoDB。中间隔了39年,文档数据库的核心理论居然早就被人算透了。

这篇论文叫《非第一范式关系数据库理论》,作者Mark A. Roth。当时关系数据库刚统治世界,所有人都在追求第一范式(1NF)——把数据拆成扁平的二维表,消灭一切嵌套结构。Roth却反着来:嵌套怎么了?嵌套才是自然状态。

MongoDB的工程师可能没读过这篇论文,但他们做出来的东西,几乎就是论文的复刻。

10行变2行:1NF的冗余有多荒唐

10行变2行:1NF的冗余有多荒唐

Roth在论文里举了个经典例子。假设你要存员工信息:姓名、孩子、技能。按1NF的规矩,孩子和技能得拆成独立表,或者拉成笛卡尔积的扁平表。

结果是10行数据,Smith出现4次,Jones出现6次。孩子的信息重复,技能的信息重复,连员工名字都重复。更新的时候漏改一行?数据就脏了。

非第一范式(¬1NF)直接把结构嵌起来:2个文档,完事。孩子是个数组,技能是个数组,技能下面的考试记录还是个数组。三层嵌套,零冗余。

Roth把这种结构叫"数据库模式"(database scheme)——属性可以是标量(零阶),也可以是关系值(高阶)。听起来像面向对象?1986年还没流行这词。Roth的动机很纯粹:省空间、省操作、符合直觉。

MongoDB的文档模型,本质上就是Roth的"数据库模式"的工业实现。

$unwind、$group、$lookup:论文里的算子活了

$unwind、$group、$lookup:论文里的算子活了

Roth的论文不只是定义了数据结构,还设计了一套代数操作。嵌套关系怎么查询?怎么聚合?怎么连接?他全写好了。

MongoDB的聚合管道,几乎一一对应:

$unwind——把嵌套数组展开成扁平流,对应论文的"unnest"操作。$group——按字段聚合,对应"nest"的逆过程。$lookup——跨集合连接,对应嵌套关系的"join"。集合操作(并、交、差)——论文里也有专门的代数定义。

这不是巧合。2010年MongoDB发布聚合框架时,工程师面对的是和Roth相同的问题:怎么在嵌套结构上跑关系代数?Roth的论文提供了现成的数学基础。

打开网易新闻 查看精彩图片

一个细节:Roth的论文里,嵌套关系的属性可以是"关系值"(relation-valued),也就是属性本身是个表。MongoDB的数组 of 文档,正好对应这个概念。数组不是事后补丁,是理论的一等公民。

为什么SQL绕了39年的弯路

为什么SQL绕了39年的弯路

1NF的暴政持续了几十年。Oracle、MySQL、PostgreSQL,全都把嵌套结构当成异端。JSON字段是后来才加的,而且用起来像二等公民——不能索引、不能高效查询、不能参与连接。

Roth早就指出了1NF的三个问题:冗余存储、更新异常、查询复杂。但SQL的惯性太大。标准委员会、企业客户、遗留系统,层层绑架。直到移动互联网爆发,数据模型越来越像JSON,文档数据库才杀回来。

MongoDB的成功常被说成"开发者体验好"。但这句评价太轻了。它的底层是扎实的代数理论,只是包装成了工程师喜欢的样子。

论文里有个概念叫"V-relation"——值关系,允许属性取空集。MongoDB的稀疏文档、动态模式,正好对应这个设计。不是MongoDB发明了灵活模式,是Roth证明了灵活模式在数学上自洽。

换句话说,MongoDB的"无模式"不是偷懒,是有理论背书的工程选择。

4NF的替代方案:Roth的野心更大

4NF的替代方案:Roth的野心更大

原文标题提到"替代4NF"。这值得展开。

数据库范式从1NF跑到4NF,是为了消除各种异常:插入异常、删除异常、更新异常。但每升一级范式,表就拆得越碎,查询时连接越多。4NF要求消灭多值依赖,实践中往往过度设计。

Roth的路线完全不同:不拆表,改数据结构。用嵌套关系直接表达多值依赖,既保留范式化的好处,又避免连接的代价。

论文里的Figure 3-1,画的是一个嵌套关系模式。把它和MongoDB的集合定义并排放,几乎能逐行对应。ename是标量,Children是文档数组,Skills是嵌套更深的数组。Exams嵌在Skills里,三层深度。

这种结构在SQL里要拆四张表:employees、children、skills、exams。查询一个员工的完整信息?四个JOIN。MongoDB呢?一个find,投影一下就行。

Roth在1986年就证明了:嵌套关系的表达能力不低于扁平关系,而且操作代价更优。但工业界花了30年才跟上。

打开网易新闻 查看精彩图片

一个冷知识:对象数据库为什么死了

一个冷知识:对象数据库为什么死了

90年代有过一阵对象数据库热潮,GemStone、ObjectStore、Versant,名字现在都没人记得。它们也想解决嵌套数据的问题,但做法太激进:把内存里的对象图直接持久化,抛弃了关系代数。

结果是查询语言一团糟,优化器做不出来,性能被SQL吊打。Roth的论文其实给过正确答案:保留关系代数,扩展它,而不是扔掉它。

MongoDB的聚合管道就是这条路线。$match、$project、$sort,全是关系操作。只是操作的对象从行变成了文档,从标量变成了嵌套结构。

对象数据库死了,文档数据库活了。区别不在于嵌套不嵌套,而在于有没有扎实的代数基础。Roth的论文提供了这个基础,MongoDB的工程师重新发现了它——或者至少,独立 converged 到了同样的设计。

有个细节挺有意思。Roth的论文引用了一篇1982年的日文论文,作者是K. Tanaka。那篇论文更早提出了非第一范式的想法,但用日语写的,国际学术界没注意到。Roth把它挖出来,用英文重写,搭好了完整的理论框架。

学术圈的传播延迟,和工业圈的采纳延迟,叠加出了39年的时差。

现在的MongoDB工程师,有多少读过Roth的论文?我猜不多。但聚合管道的每一个操作符,都在执行论文里的数学定义。$unwind不是魔法,是unnest。$group不是黑科技,是aggregate with grouping。

这像是建筑工人没读过结构力学,但盖的楼正好符合力学原理。工程直觉和数学理论,有时候会在终点相遇。

1986年的论文,现在还在arXiv上能下到。39页,定义、定理、证明,标准的学术格式。最后一节是"Future Work",Roth列了几个开放问题:递归查询、完整性约束、分布式实现。

递归查询后来有了$graphLookup。完整性约束MongoDB加了Schema Validation。分布式?那是另一个故事了,CAP定理的战场。

Roth在2000年代初离开了学术界,去了工业界。他的LinkedIn停更在2015年。不知道他有没有用过MongoDB,有没有意识到自己的理论变成了几十亿市值产品的地基。

如果文档数据库早点流行,Roth的名字会不会像Codd一样家喻户晓?历史没有如果。但读他的论文,你能感觉到一种错位:正确的想法,生在了错误的时间。

现在MongoDB的文档里,从来没提过Roth。宣传材料讲"为开发者设计",讲"灵活的数据模型",不讲代数、不讲范式、不讲1986年的那篇论文。这也没错,工程师不需要知道理论来源,就像用哈希表不需要知道Knuth。

但知道来源之后,你看MongoDB的眼神会变。那些"自然而然"的设计选择,原来都有出处。$unwind的命名有点怪?unnest更怪,但至少诚实。数组嵌套的限制、聚合管道的执行顺序、内存使用的边界条件——全能在论文里找到原型。

最后留个开放问题:如果你的数据天然是嵌套的,你会为了迎合SQL的范式,硬拆成十几张表,还是直接用文档模型,让结构跟着数据走?Roth在1986年投了后者一票,39年后,大多数新系统也这么选了。但银行核心交易、财务总账这些场景,SQL still rules。是场景真的不同,还是惯性还没打破?