01

資料模型起手式:關聯式 vs 文件式

同一份資料,換一種「組織術」,整個系統的命運就不一樣

語言的界線,就是世界的界線

第二章一開頭,作者沒有先丟術語,而是引了一句哲學家的話當題詞。乍看跟資料庫沒關係,但讀完整章你會發現它其實是核心隱喻:你選的 資料模型, 就是你能用來「描述世界」的語言——選錯語言,有些事情你連說都說不出來。

原文 · DDIA Ch.2 題詞 “The limits of my language mean the limits of my world.” — Ludwig Wittgenstein, Tractatus Logico-Philosophicus (1922) Data models are perhaps the most important part of developing software, because they have such a profound effect: not only on how the software is written, but also on how we think about the problem that we are solving.
白話翻譯

「我的語言的界線,就是我的世界的界線。」

——維根斯坦,《邏輯哲學論》(1922)

資料模型很可能是開發軟體時最重要的一環,因為它的影響極其深遠:不只決定程式怎麼寫,還決定我們怎麼「思考」眼前要解決的問題。

🗺️
先給你一個畫面

把資料模型想成你經營的「餐飲帝國」裡,從顧客點餐心願到餐桌上菜的整套組織術:顧客的心願要變成服務生的點菜單,點菜單要變成廚師看得懂的食譜,食譜又要變成倉庫裡實際擺放的食材。每一層都是一次「翻譯」,而抽象化就是讓你不用管底層怎麼運作的魔法。

資料的「變身」之旅:從心願到電流

你的資料不會直接從現實世界跳進硬碟。它會經過好幾層翻譯,每一層都比下一層更簡潔、更抽象。

1
真實世界概念

顧客腦中那句「我要一份客製化義大利麵,少辣多起司」——人、組織、商品、行為,原汁原味的需求。

2
應用程式資料結構

服務生把心願寫成標準化的點菜單:欄位記錄菜名、辣度、起司量。這是你程式碼裡的物件與 API。

3
通用資料模型

廚師不看點菜單做菜,他照通用資料模型(套餐式、食材清單式、關係圖譜式)來理解——這正是本模組的主角:關聯式 vs 文件式。

4
資料庫底層表示

庫房經理把抽象的「起司」變成「一塊 500 克的高達,放在 B 區冷凍櫃第三層」——資料庫工程師把模型轉成記憶體與硬碟上的位元組。

💡
為什麼要在乎這麼多層?

每種資料模型都內建「預設的使用情境和假設」——選對了,某些操作快得不可思議;選錯了,同樣的操作可能慢到難以忍受,甚至根本做不到。這就是為什麼整本書要花一整章講這件事。

兩大典範,點開看懂彼此的脾氣

第二章的主戰場,就是這兩種通用資料模型的較量。點點看,比較它們的定義與最適合的情境。

🗂️ 關聯模型 Relational
📄 文件模型 Document
點擊上面的卡片,看看兩種典範各自的定義與最適合的情境。

用一個比喻分高下:關聯模型像超級圖書館的標準化檔案系統——每個主題分區放好,靠「讀者編號」「ISBN」這類唯一識別碼互相牽線;文件模型則像你的私人日記——一天的行程、心情、照片全寫在同一頁,翻開就是全貌。

🧩
Schema 彈性

文件模型多半是Schema-on-read,今天能塞流水帳,明天能貼照片,事後看得懂就好;不必像關聯式那樣每次改格式都要動全資料庫的表格。

📍
資料在地性

相關資料整包存在一起,一次讀取就拿到全貌——像做三明治時食材全放同一個籃子,伸手就拿,不用跑冰箱又跑櫥櫃。

🧱
貼近物件

JSON 文件的樹狀結構天生就接近你程式裡的物件,能有效減少把物件拆進表格、再從表格組裝回物件的麻煩翻譯。

關聯式怎麼運作:表格、鍵、JOIN

關聯模型靠三個角色撐起整套系統:主鍵讓每筆記錄獨一無二,外鍵把不同表格串起來,JOIN則是真正把資料兜在一起的「連接器」。

你只需要告訴資料庫「我要什麼」,不用管「怎麼找」——這叫宣告式查詢。底層有個「查詢最佳化器」,像圖書館智能查詢機背後的大腦,自動規劃最快的路徑。這正是關聯模型加上 SQL 能稱霸資料庫世界幾十年的關鍵:你少操心「怎麼做」,資料庫升級了索引或執行策略,你的查詢一行都不用改就能變快。

⚠️
代價:阻抗不匹配

你的程式碼習慣用物件思考——一個使用者物件裡塞著姓名、工作經歷、學歷。但存進關聯式資料庫前,得先把它「拆解」成 users、positions、education 好幾張表格,讀出來時再「組裝」回物件。這種來回翻譯的麻煩,就是阻抗不匹配——這正是文件模型想解決的痛點。

文件模型怎麼運作:自包含、彈性、貼近物件

文件模型的興起,是NoSQL運動的一部分,背後三個推力:資料量與併發暴增、開發節奏要求 Schema 能跟著快速迭代、以及前面提到的阻抗不匹配。一份 JSON 文件就能把使用者的姓名、工作經歷、學歷全部打包在一起——自包含、可以是多層樹狀結構,特別適合表達「一對多」關係,例如一個人有多份工作經歷。

但文件模型不是萬靈丹。它在處理「多對多」關係時通常比較吃力——如果你的資料常需要在多個實體之間頻繁地「跳轉」查詢(例如找出某供應商相關的所有訂單,再找出這些訂單裡所有顧客的詳細資料),關聯模型的 JOIN 反而更得心應手。

🥪
沒有銀彈

很多大型應用會讓不同部分的資料用不同模型存——這叫「多模型持久化(Polyglot Persistence)」。例如使用者個人檔案用文件模型、課程先修關係用圖形模型、測驗排名用關聯模型。選型不是信仰之爭,是看資料的形狀和你最常做的操作。

小試身手

兩種典範的脾氣摸得差不多了,來兩題檢查一下:

當應用程式幾乎總是「一次讀取整份使用者資料」時,文件模型相較於把資料拆到多張表的關聯模型,主要的效能優勢是什麼?
一個欄位天天在變的早期新創產品,文件模型「不強制固定 Schema」的特性,帶來的主要價值是什麼?
🤔
下一站:兩難

關聯與文件兩大家族各有優點,但現實世界的資料常常兩邊都要——接下來看看這種「兩難」會踩到哪些坑。

02

物件關係不匹配:資料組織的兩難

程式碼愛用「物件」、資料庫愛用「表格」——當兩種方言互相翻譯,你得學會挑邊站

兩種「方言」,同一份履歷

你的程式碼裡,一個使用者是一顆完整的 物件 ——姓名、頭像、底下還掛著一串工作經歷、一串學歷。但資料庫裡的 關係型資料庫 只認得「表格」:一張 users 表、一張 positions 表,彼此用 id 互相指。這種結構上的落差,書裡稱為 物件關係不匹配

原文 · DDIA Ch.2 Most application development today is done in object-oriented programming languages, which leads to a common criticism of the SQL data model: if data is stored in relational tables, an awkward translation layer is required between the objects in the application code and the database model of tables, rows, and columns. The disconnect between the models is sometimes called an impedance mismatch. Object-relational mapping (ORM) frameworks like ActiveRecord and Hibernate reduce the amount of boilerplate code required for this translation layer, but they can’t completely hide the differences between the two models. However, most people have had more than one job in their career (positions), and people may have varying numbers of periods of education and any number of pieces of contact information. There is a one-to-many relationship from the user to these items.
白話翻譯

今天大多數應用程式都是用物件導向語言寫的,這帶來一個對 SQL 資料模型常見的批評:如果資料存在關係型表格裡,程式碼裡的物件跟資料庫的表格、列、欄之間,就需要一層彆扭的轉換。

這種模型之間的落差,有時被稱為「阻抗不匹配」。

像 ActiveRecord、Hibernate 這類 ORM 框架,能減少這層轉換需要寫的樣板程式碼,但沒辦法完全藏住兩種模型之間的差異。

然而,大多數人一生不只做過一份工作(多段工作經歷),教育背景的期數也因人而異,聯絡方式更可能有好幾筆。

使用者跟這些項目之間,是一種一對多的關係。

🍽️
先給你一個畫面

你跟服務生說「我要一份主廚特製套餐」,但後廚的庫存系統只認得「牛排一份」「蘑菇湯一碗」這種單一品項——套餐是你腦中的完整想像,後廚卻只懂拆開來的食材清單。這就是物件與表格的「方言差異」。

用 LinkedIn 個人檔案拆給你看

想像一份 LinkedIn 履歷:姓名、自我介紹是「一人一筆」的簡單欄位,但「工作經歷」「學歷」「聯絡方式」卻是「一人好幾筆」——這就是 一對多關係。 物件導向程式碼很自然地把它們都包進同一個 UserProfile 物件裡,變成一棵樹;但關係型資料庫為了 正規化, 會把它拆成 users、positions、education 三張各自獨立的表格,用 user_id 當外鍵互相指認。

程式碼怎麼想

一個 UserProfile 物件,底下直接掛著 positions 清單、education 清單——一次拿到、一次看完。

資料庫怎麼存

users、positions、education 各自一張表,靠 user_id 這個外鍵互相連結,沒有人「整包」存。

讀回來要做的事

先查 users 拿到基本資料,再用 user_id 分別查 positions、education,最後在程式碼裡手動拼回一個完整物件。

🧑‍💼
服務生=ORM

ORM 就像餐廳的服務生:把你的「套餐」訂單(物件)拆成後廚聽得懂的單品指令(多張表的 SQL 操作),出餐時再把零散菜色組裝回一份套餐端給你。它能省去大量手動翻譯的力氣,但沒辦法讓兩種「方言」變成同一種——差異還是在,只是被藏起來一部分。

換個容器:文件資料庫的「便當盒」解法

如果餐廳改賣「客製化便當盒」呢?你點的整份套餐直接打包成一盒——這就是 文件型資料庫 的概念。一個 UserProfile 物件,可以直接存成一份 JSON 文件,positions、education 都內嵌在裡面,讀的時候一次整份拿出來,幾乎不用「翻譯」。

但便當盒也有它的麻煩:如果某道菜(例如「公司」)本身是個獨立實體,需要被很多份履歷共用、又要隨時更新最新 logo,那把它直接寫死在每份便當盒裡,就會重新製造出「資料重複」的問題——這正是 資料在地性 換來的代價:跨文件的複雜關聯,文件資料庫天生不擅長。

🗂️
關係型表格

資料拆開存、靠外鍵連結,正規化程度高,多對多關聯靠 JOIN 一次處理得漂亮。

📦
文件型 JSON

整包存在一起,貼近程式裡的物件樹狀結構,一次讀取就拿到全部,但跨文件關聯較吃力。

⚠️
便當盒不是萬靈丹

履歷內嵌進文件可以解決「一人多筆工作經歷」這種一對多關係;但如果「公司」要變成有自己頁面、能被搜尋、能被多人引用的獨立實體,文件模型反而要在應用程式層自己模擬關聯查詢,相當繁瑣。

動手選一選:這種資料情境,該用哪種因應策略?

下面四種常見的資料情境,各自比較適合哪種因應策略?拖拖看,配完按「對答案」。

正規化+外鍵
去正規化內嵌
Schema-on-Read 文件模型
Schema-on-Write 嚴格欄位
履歷裡「多段工作經歷」這種一人多筆、通常整份一起讀取顯示的資料
拖到這裡
「公司」是獨立實體,會被多位使用者的履歷共同引用,且要隨時顯示最新 logo/名稱
拖到這裡
產品還在快速迭代,欄位常常要改、不同來源的資料長相也不太一樣
拖到這裡
金融交易、庫存這類要求高度一致性、結構穩定不太會變的資料
拖到這裡

另一道兩難:先檢查再存,還是先存再說?

除了「物件 vs 表格」,資料模型還有第二層選擇:資料的 Schema 要在「寫入前」就嚴格把關,還是「讀取時」才由程式自己解讀?

Schema-on-Write 像超市進貨——倉管會逐項核對品名、規格,不合規格直接拒收,不讓它進倉庫。 Schema-on-Read 則像超市結帳——你把什麼都丟進購物車都行,店員直到結帳掃描那一刻,才需要搞懂你買了什麼。

🔓
Schema-on-Read

寫入超自由,彈性高,適合原型開發、IoT 感測資料、日誌這種結構常變、來源混雜的場景。代價是讀取時得自己處理「缺欄位」「舊格式」這種異質資料。

🔒
Schema-on-Write

寫入前就把關,資料品質、一致性有保證,適合金融交易、庫存、權限這種高一致性需求。代價是要改結構時,得規劃遷移(migration),成本較高。

🧳
遷移(Migration)不是洪水猛獸

Schema-on-Write 要改結構,得像搬家一樣規劃:新增欄位、搬資料、確認沒問題才能拆舊欄位。Schema-on-Read 則把這份麻煩往後拖到「讀取那一刻」,由程式自己判斷舊資料該怎麼解讀——彈性和一致性,永遠是一場交換。

小試身手

物件、表格、文件、Schema 的兩難,你抓到核心了嗎?來兩題。

「物件關係不匹配」最核心的意思是什麼?
一批資料因為來源系統出錯,金額欄位混入了文字。哪一種策略會在「寫入當下」就攔下這筆錯誤資料?
🔍
下一站:怎麼「問」資料庫問題?

講完「資料長什麼樣子」,接下來換個角度——你要怎麼「問」資料庫問題?往下捲,進入查詢語言的世界。

03

查詢語言大解密:SQL 與 MapReduce

「教廚師怎麼做菜」還是「直接點菜」——兩種跟資料庫對話的風格,決定了誰來操心效能

跟資料庫溝通,其實只有兩種口氣

前面兩站我們搞懂了資料「長什麼樣子」——關聯式的表格、文件式的巢狀結構。這一站換個角度:資料擺好之後,你要怎麼「問」它問題?

說也奇怪,幾乎所有跟電腦對話的方式,攤開來看都只有兩種口氣。一種是 命令式 ——你像個事必躬親的將軍,一步步下令「先做這個、再做那個」;另一種是 宣告式 ——你只說出你想要的結果,剩下的細節丟給系統自己想辦法。

🍽️
先給你一個畫面:點餐 vs. 進廚房教做菜

命令式,就像你親自走進廚房,告訴主廚「先拿雞肉、切丁、熱鍋、炒到半熟、加番茄醬燉十五分鐘」——每個動作都你說了算。宣告式,就像你坐在餐桌前翻菜單,只說「我要一份經典番茄燉雞」,主廚(也就是資料庫的 查詢優化器 )會自己判斷最快、最好的做法。

直接讀原文,旁邊就是白話

書裡用一段很乾脆的對比,把「宣告式」講得很透徹——尤其是它「藏起怎麼做」反而帶來的好處。一句對一句看:

原文 · DDIA Ch.2 An imperative language tells the computer to perform certain operations in a certain order. You can imagine stepping through the code line by line, evaluating conditions, updating variables, and deciding whether to go around the loop one more time. In a declarative query language, like SQL or relational algebra, you just specify the pattern of the data you want... but not how to achieve that goal. It is up to the database system's query optimizer to decide which indexes and which join methods to use, and in which order to execute various parts of the query. A declarative query language is attractive because it is typically more concise and easier to work with than an imperative API.
白話翻譯

命令式語言會叫電腦照特定順序執行某些操作。

你可以想像成一行一行讀過程式碼:判斷條件、更新變數、決定要不要再跑一次迴圈。

而像 SQL 或關聯式代數這種宣告式查詢語言,你只要指定你想要的資料長什麼樣子……不必說「怎麼達成」。

該用哪個索引、用哪種聯結方式、查詢的各部分要照什麼順序執行——這些都交給資料庫的查詢優化器自己決定。

宣告式查詢語言之所以吸引人,是因為它通常比命令式 API 更簡潔、更好駕馭。

💡
藏起來,其實是一種「禮物」

你可能覺得「藏起怎麼做」是失去掌控權,但書裡的重點剛好相反——正因為你沒寫死步驟,資料庫日後想換更快的演算法、加新索引,甚至把查詢拆成多核心並行跑,都不必驚動你的程式碼一行。

同一個任務,兩種寫法:找出所有「鯊魚」

說再多比喻,不如看一次真正的程式碼。假設有一份動物清單,我們想找出所有 family 是「Sharks」的動物——兩種風格的程式碼,長相完全不同。

🪖
命令式:一步步教電腦怎麼找

先準備一個空籃子,逐一檢查每隻動物,符合條件就放進去,最後把籃子交出來——每個動作都得你來寫。

🪄
宣告式:只說你要什麼

SELECT * FROM animals WHERE family = 'Sharks'; ——一句話講完「我要什麼」,怎麼找、要不要用索引,資料庫自己決定。

⚠️
命令式不是「比較爛」

命令式在處理複雜、特殊的流程控制時依然有它的彈性;只是對「找符合條件的資料」這種常見任務,宣告式通常更簡潔、更好維護,也更容易被資料庫拿去自動優化、甚至平行處理。

SQL 的三招「許願術」

SQL(Structured Query Language)是宣告式查詢語言裡最經典的代表。你不需要懂資料庫內部怎麼運作,只要學會「怎麼許願」。以一個學生成績系統為例:

1
精準篩選

SELECT name, score FROM students WHERE gender = 'Female' AND score >= 90; —— 只說條件,資料庫負責找。

2
輕鬆統計

SELECT class_id, AVG(score) FROM students GROUP BY class_id; —— 一句話算出每班平均,不必自己寫迴圈分類加總。

3
多表聯結

SELECT s.name, c.class_name FROM students s JOIN classes c ON s.class_id = c.id; —— 把兩張表「接」起來看,細節交給資料庫。

🧠
幕後有個聰明大腦在幫你

每一句 SQL 背後,查詢優化器都在悄悄分析:該用哪個索引、先查哪張表、要不要拆成多核心並行——這正是命令式程式很難自動做到的事,因為步驟早就被你寫死了。

MapReduce:界於命令式與宣告式之間的「分工合作」

那文件資料庫呢?像 MapReduce 這種工具,介於兩種風格中間——你得自己寫 mapreduce 兩段 JavaScript 函式,但拆分、分組、平行執行的工作仍由系統代勞。拆開看,其實就是三個動作:

Map
逐一檢查、挑出重點

對每一筆觀測紀錄執行 map 函數:如果 family 是「Sharks」,就把「年-月」當作鍵,把數量發出去。

Emit
貼標籤、丟進對應的桶

emit(key, value) 把這筆結果連同它的鍵值對交出去,系統會自動把相同鍵的值集中放在一起,準備交給下一階段。

Reduce
同一桶的全部加總

對每個分組鍵執行 reduce 函數,把同一個「年-月」底下所有的數量加起來,得到那個月的鯊魚總數。

📋
原始觀測紀錄
🔎
map 函式
🏷️
emit 分組
reduce 函式
📊
每月總數報表
按「下一步」看一筆鯊魚紀錄怎麼被算進月報表
🔒
關鍵限制:map 和 reduce 必須是「純函數」

它們不能偷偷修改外部狀態,也不能在裡面另外查資料庫——這條規矩聽起來很硬,卻是換來安全感的代價:系統才能放心把工作切成一塊塊平行處理,就算某一塊失敗了,重跑一次也不會搞亂結果。

正因為自己寫 mapreduce 比較繁瑣,後來 MongoDB 這類資料庫又推出了更宣告式的 聚合管線(Aggregation Pipeline) ——用 $match$group 這些階段組成一條生產線,寫法更接近 SQL,也更容易被資料庫自動優化。

小試身手

分清楚「誰在說怎麼做、誰只說要什麼」,你就抓到這一站的核心了。來兩題:

寫法 A:用 for 迴圈逐一檢查陣列、符合條件就加入結果。寫法 B:SELECT * FROM animals WHERE family = 'Sharks'。下列敘述何者正確?
MapReduce 要求 map 和 reduce 函數必須是「純函數」(不能有副作用、不能另外查資料庫)。這個限制最主要是為了什麼?
🕸️
下一站:當資料本身就是一張網

關聯式與文件式模型,骨子裡都還是「表格/樹狀」思維——就算查詢語言再聰明,遇到「朋友的朋友」這種錯綜複雜的多對多關係,還是會堆出一大堆 JOIN。如果資料本身就長得像一張關係網呢?往下捲,進入圖形資料模型。

04

圖形資料模型:Cypher、SPARQL、Datalog

當資料長得像一張錯綜複雜的關係網,「點」和「線」會比表格更誠實

當「多對多」多到爆炸,表格會先喊累

前面幾站我們看過關聯模型和文件模型怎麼處理一對多、甚至簡單的多對多關係。但如果你的應用裡,幾乎「每樣東西都跟很多別的東西有關係」——朋友的朋友、誰住在哪裡又屬於哪個國家、誰買了同系列的哪些商品——關聯模型就算還能硬撐,也會堆出一大堆 JOIN,越查越慢、程式碼越寫越擰巴。

這種「高度互聯」的資料,換一種想法會自然很多:把每樣東西都當成頂點,把它們之間的關係畫成一條條——這就是圖形資料模型

🗺️
先給你一個畫面:城市導航地圖

你的導航 App 就是一個活生生的圖形資料庫。地圖上的地點(你家、咖啡館、公司)是頂點,連接它們的道路是邊;每個地點有自己的營業時間、評價,每條路有自己的長度、路況——這些都是屬性。按下「導航」,App 就是沿著邊一段段「走」,幫你找出最佳路線。

原文怎麼說:document 笨拙、relational 還行、graph 最自然

這句判斷,是整節的核心結論,直接從書裡搬出來給你看:

原文 · DDIA Ch.2 It's not possible to say in general which data model leads to simpler application code; it depends on the kinds of relationships that exist between data items. For highly interconnected data, the document model is awkward, the relational model is acceptable, and graph models are the most natural. A graph consists of two kinds of objects: vertices (also known as nodes or entities) and edges (also known as relationships or arcs). An equally powerful use of graphs is to provide a consistent way of storing completely different types of objects in a single datastore.
白話翻譯

沒有哪個資料模型「天生比較簡單」這回事——關鍵看你的資料之間到底有什麼樣的關聯。

如果資料高度互聯:文件模型會很笨拙,關聯模型勉強能接受,圖形模型則是最自然的選擇。

一張圖由兩種東西組成:頂點(也叫節點或實體),以及邊(也叫關係)。

圖形還有一個同樣強大的用法:把完全不同種類的東西,全部一致地存進同一個資料庫裡。

💡
這不是「圖形最強」,是「對的工具」

書裡沒有說圖形資料庫贏過一切——它只在「關係本身就是重點」的情境裡最自然。員工薪資表這種彼此獨立的資料,關聯式資料庫照樣輕鬆。

招牌互動:拆開一張屬性圖(Property Graph)

屬性圖模型是最直觀的圖形資料模型,由 Neo4j 這類資料庫實作。它就三個零件,點開每一個看看定義:

🔵 頂點 Vertex
➡️ 邊 Edge
🏷️ 屬性 Property
點一個零件,看它在屬性圖裡到底是什麼角色。
🕵️
偵探的比喻:人物檔案+關係速寫

把每個人想成一份檔案(頂點),檔案裡寫滿姓名、職業(屬性)。當兩人有關係,就在兩份檔案之間畫一條線(邊),線上貼著「朋友」這種標籤,旁邊還能寫「認識日期:2020 年」(邊的屬性)。要追查某人的社交圈,直接從他的檔案沿著線往外查就好。

還有一種相近但更「原子化」的模型,叫三元組儲存:把每件事都拆成「主詞、謂詞、受詞」三段式陳述,例如 `(lucy, age, 33)`、`(lucy, marriedTo, alain)`。謂詞既可以是屬性鍵,也可以是邊的關係類型——本質上跟屬性圖是同一件事的另一種寫法。

跟著查詢走一遍:朋友的朋友的朋友

Cypher 是屬性圖專用的查詢語言,語法就像在「畫」一張關係圖:`(a)-[:FRIEND_OF]->(b)` 一眼就懂。下面按「下一步」,看資料庫怎麼沿著邊,從小明一路走到他朋友的朋友。

🧑
小明 me
🧑‍🤝‍🧑
朋友 friend
🧑‍🤝‍🧑
朋友的朋友 fof
推薦結果
按「下一步」開始走訪
⚠️
換成 SQL 會多痛?

同樣這個查詢,關聯式資料庫得對「朋友關係」這張表自己 JOIN 自己兩次(甚至更多次),才能走兩步關係。圖形資料庫把「邊」當成第一級結構,沿著邊走訪天生就快、天生就好寫。

三種「魔咒」,同一種精神:宣告式

Cypher、SPARQLDatalog 都是宣告式查詢語言——你只描述「想要什麼樣的關係模式」,剩下交給資料庫自己找最佳路徑去走。就像點菜,你說「我要一份義大利麵」,不用教廚師怎麼煮。

Cypher:用符號畫圖

頂點用 `()`、邊用 `-[]->`。`(lucy)-[:BORN_IN]->(idaho)` 一看就懂是「Lucy 出生在 Idaho」。專為屬性圖設計,Neo4j 的查詢語言。

SPARQL:陳述事實清單

專為三元組儲存設計,用一串「主詞 謂詞 受詞」陳述句描述已知條件,例如 `?person :bornIn ?place`,再讓資料庫拼出符合的答案。

可變長度路徑:任意走幾步都行

Cypher 的 `[:WITHIN*0..]`、SPARQL 的 `:within*`,都能表達「沿著這種關係,走 0 次或多次」——城市在州裡、州在國裡,不管疊幾層都一次查完,SQL 得寫死固定次數 JOIN 才能逼近。

🧭
同一個問題,兩種「方言」

書裡的例子「找出所有從美國移民到歐洲的人」:Cypher 寫成 `(person)-[:BORN_IN]->()-[:WITHIN*0..]->(us)` 配上 `LIVES_IN` 那一半;SPARQL 則寫成 `?person :bornIn / :within* / :name "United States"`。語法不同,講的是同一件事——沿著「位於」關係一路往上找,不管中間隔幾層。

Datalog:用「事實+規則」推理出答案

Datalog 不像 Cypher、SPARQL 那麼家喻戶曉,卻是它們的靈感源頭。它的世界只有兩種積木:事實規則。規則能從已知事實一路「推導」出新事實,特別擅長處理像家譜樹這種層數不固定的結構。

📋
事實 Facts

不需要推論、本來就確定的資訊。例如 `name(idaho, 'Idaho')`、`within(idaho, usa)`——就是偵探手上的筆錄。

🧩
規則 Rules

「如果前提成立,結論就成立」。`within_recursive(L,N) :- within(L,Via), within_recursive(Via,N)` 讓「位於」可以一層層往上疊。

🔁
遞迴 Recursion

規則引用自己,像俄羅斯娃娃一層包一層,自動涵蓋「不知道有幾層」的祖先、行政區劃,不用事先寫死層數。

🕵️‍♂️
名偵探柯南的辦案邏輯

事實=已知線索;規則=偵探腦中的推理法則;查詢=偵探想知道的問題。給定 `parent(Alice, Bob)`、`parent(Bob, Carol)` 和規則 `grandparent(X,Z) :- parent(X,Y), parent(Y,Z)`,引擎會自己湊出 `grandparent(Alice, Carol)`——這個新事實,沒有人手動寫過。

小試身手

圖形模型、屬性圖、Cypher、Datalog 都認識了一輪,來兩題檢查一下:

為什麼「找出某人朋友的朋友的朋友」這類查詢,圖形資料模型比傳統關聯式資料庫更有優勢?
Cypher 查詢 `(a)-[:FRIENDS_WITH]->(b)` 並未指明資料庫該先從哪個頂點開始、怎麼走。這體現了什麼特性?
🔀
下一站:到底該選誰?

四種模型——關聯式、文件式、查詢語言、圖形——全部攤開來,到底什麼情境該選誰?往下捲,幫你的下一個專案做決定。

05

大局:三種資料模型怎麼選

關聯、文件、圖形——不是誰比較先進,是誰比較懂你的資料長什麼樣

把整章倒帶一次:你其實學了一條「翻譯鏈」

這一章繞了一大圈,其實都在講同一件事:你腦中的真實世界概念,要經過好幾層 抽象化, 才能變成硬碟裡的位元組。每一層都用自己的 資料模型 說話——應用程式講物件,資料庫講 JSON/表格/圖形,硬體講電流脈衝。

而我們在中間這一層「通用資料模型」,其實只有三個主要選手反覆出現:關聯式文件圖形。這個模組不會再逐一教細節,而是把它們攤開來放在同一張桌上,幫你建立「什麼情境選什麼」的判斷力——這是你讀完整章最值錢的能力。

🗺️
先說結論

沒有一種資料模型是「正解」,只有「跟你的資料形狀match不match」。選錯不會馬上爆炸,但會讓你之後每一個查詢都在跟模型「對抗」。

書裡是怎麼收尾這個選擇題的

原文在談完關聯式與文件模型的拉鋸後,用一段話把「什麼時候該換成圖形模型」講得很乾脆。直接看原文,旁邊配白話。

原文 · DDIA Ch.2 We saw earlier that many-to-many relationships are an important distinguishing feature between different data models. If your application has mostly one-to-many relationships (tree-structured data) or no relationships between records, the document model is appropriate. But what if many-to-many relationships are very common in your data? The relational model can handle simple cases of many-to-many relationships, but as the connections within your data become more complex, it becomes more natural to start modeling your data as a graph. A graph consists of two kinds of objects: vertices (also known as nodes or entities) and edges (also known as relationships or arcs).
白話翻譯

前面看到了:「多對多關係」是區分不同資料模型最關鍵的特徵。

如果你的應用大多是「一對多」(樹狀結構),或紀錄之間根本沒什麼關係,文件模型就很合適。

但如果你的資料裡,多對多關係非常普遍呢?

關聯模型能應付簡單的多對多,但當資料裡的連結越來越複雜,把它建模成「圖形」會自然得多。

一個圖形,由兩種東西組成:頂點(也叫節點、實體)和邊(也叫關係)。

💡
這句話就是整章的判斷準則

樹狀、自成一體 → 文件;關係明確但不太複雜、要強一致性 → 關聯;關係本身就是重點、而且盤根錯節 → 圖形。整章其實就是在教你認出這三種「形狀」。

三選手並排站:點點看,每個的「個性」是什麼

把整章學到的三種通用資料模型放在同一張圖上。點下去看每個的白話總結。

🗂️ 關聯模型 Relational
📄 文件模型 Document
🕸️ 圖形模型 Graph
點上面任一個模型,看它的個性、強項與代價。
⚠️
界線正在融合,但「個性」還在

PostgreSQL 加了 JSONB 欄位,MongoDB 加了 聚合管線, 兩邊都在互相學習。但這不代表它們變成同一種東西——選型時還是要回到「我的資料、我的查詢模式,骨子裡比較像哪一種形狀」。

整章總複習:把情境拖到對的資料模型上

這是這個模組的招牌互動。下面四個應用情境,憑你對整章的理解,拖到它最自然的資料模型。有一題刻意設計成「兩種模型都說得通」,留意選項說明。

圖形模型
文件模型
關聯模型
關聯或圖形皆可
社交網絡:使用者彼此是「朋友」,還要能查出「朋友的朋友」、做交友推薦
拖到這裡
使用者個人檔案/部落格貼文:每筆資料自成一體,結構常常變動,極少跟別篇互相關聯
拖到這裡
訂單與帳務系統:金額、庫存、交易紀錄要強一致性,禁不起資料兜不起來
拖到這裡
商品推薦關係:「買了這個的人也買了那個」,關係會隨時間持續長出新的連結、越來越複雜
拖到這裡
🔁
最後一題的彩蛋

「商品推薦關係」一開始用關聯模型存簡單的多對多完全沒問題;但隨著推薦邏輯越長越複雜、關係層層交織,書裡說得很白:這時候「自然地」會開始把資料建模成圖形。模型不是一輩子綁死的選擇。

整章關鍵字快閃複習

四個你在整章反覆遇到的概念,最後再串一次:

🧩
阻抗不匹配

阻抗不匹配提醒我們:選模型不只是選資料庫,也是選「跟你程式碼合不合拍」。

🗣️
宣告式 vs 命令式

不管選哪種資料模型,宣告式查詢(像 SQL、Cypher)都讓你專注在「要什麼」,把「怎麼做」交給查詢優化器

🔀
多型持久化

大型系統常常不只用一種模型——這節課看到關聯式資料庫長出 JSON 支援、文件資料庫長出 join,界線一直在模糊。

🕸️
多對多是分水嶺

一對多選文件,簡單多對多關聯也行,但頂點串起來的複雜關係,圖形模型最吃得開。

整章總複習:兩題定生死

這兩題故意跨小節出,看看你是不是真的把「選型判斷力」內化了。

整章看下來,決定該用關聯、文件還是圖形模型的核心判斷依據是什麼?
為什麼「找出朋友的朋友的朋友」這種查詢,用圖形資料庫做通常比用傳統關聯式資料庫的多重 JOIN 更自然有效率?
💾
下一站(其他章)

資料怎麼「長」出來了——你現在會挑形狀、會選模型。下一步是:它實際怎麼被儲存與找到?我們要鑽進儲存引擎的底層,看資料庫收到一筆寫入之後,到底把它放在硬碟的哪裡、又是怎麼幾毫秒內把它翻出來。