![]()
1986年的論文,2025年的MongoDB。中間隔了39年,文檔數據庫的核心理論居然早就被人算透了。
這篇論文叫《非第一范式關系數據庫理論》,作者Mark A. Roth。當時關系數據庫剛統治世界,所有人都在追求第一范式(1NF)——把數據拆成扁平的二維表,消滅一切嵌套結構。Roth卻反著來:嵌套怎么了?嵌套才是自然狀態。
MongoDB的工程師可能沒讀過這篇論文,但他們做出來的東西,幾乎就是論文的復刻。
10行變2行:1NF的冗余有多荒唐
Roth在論文里舉了個經典例子。假設你要存員工信息:姓名、孩子、技能。按1NF的規矩,孩子和技能得拆成獨立表,或者拉成笛卡爾積的扁平表。
結果是10行數據,Smith出現4次,Jones出現6次。孩子的信息重復,技能的信息重復,連員工名字都重復。更新的時候漏改一行?數據就臟了。
非第一范式(?1NF)直接把結構嵌起來:2個文檔,完事。孩子是個數組,技能是個數組,技能下面的考試記錄還是個數組。三層嵌套,零冗余。
Roth把這種結構叫"數據庫模式"(database scheme)——屬性可以是標量(零階),也可以是關系值(高階)。聽起來像面向對象?1986年還沒流行這詞。Roth的動機很純粹:省空間、省操作、符合直覺。
MongoDB的文檔模型,本質上就是Roth的"數據庫模式"的工業實現。
$unwind、$group、$lookup:論文里的算子活了
Roth的論文不只是定義了數據結構,還設計了一套代數操作。嵌套關系怎么查詢?怎么聚合?怎么連接?他全寫好了。
MongoDB的聚合管道,幾乎一一對應:
$unwind——把嵌套數組展開成扁平流,對應論文的"unnest"操作。$group——按字段聚合,對應"nest"的逆過程。$lookup——跨集合連接,對應嵌套關系的"join"。集合操作(并、交、差)——論文里也有專門的代數定義。
這不是巧合。2010年MongoDB發布聚合框架時,工程師面對的是和Roth相同的問題:怎么在嵌套結構上跑關系代數?Roth的論文提供了現成的數學基礎。
![]()
一個細節:Roth的論文里,嵌套關系的屬性可以是"關系值"(relation-valued),也就是屬性本身是個表。MongoDB的數組 of 文檔,正好對應這個概念。數組不是事后補丁,是理論的一等公民。
為什么SQL繞了39年的彎路
1NF的暴政持續了幾十年。Oracle、MySQL、PostgreSQL,全都把嵌套結構當成異端。JSON字段是后來才加的,而且用起來像二等公民——不能索引、不能高效查詢、不能參與連接。
Roth早就指出了1NF的三個問題:冗余存儲、更新異常、查詢復雜。但SQL的慣性太大。標準委員會、企業客戶、遺留系統,層層綁架。直到移動互聯網爆發,數據模型越來越像JSON,文檔數據庫才殺回來。
MongoDB的成功常被說成"開發者體驗好"。但這句評價太輕了。它的底層是扎實的代數理論,只是包裝成了工程師喜歡的樣子。
論文里有個概念叫"V-relation"——值關系,允許屬性取空集。MongoDB的稀疏文檔、動態模式,正好對應這個設計。不是MongoDB發明了靈活模式,是Roth證明了靈活模式在數學上自洽。
換句話說,MongoDB的"無模式"不是偷懶,是有理論背書的工程選擇。
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。是場景真的不同,還是慣性還沒打破?
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.