Skip to content

設計到第三範式,然後適度回退

另有 28 種語言版本

English · Español · 日本語 · Português (Brasil) · Français · Deutsch · Italiano · 한국어 · 简体中文 · Português (Portugal) · Bahasa Indonesia · Türkçe · Polski · Tiếng Việt · ไทย · Nederlands · العربية · עברית · Українська · हिन्दी · Русский · Svenska · Dansk · Norsk bokmål · Suomi · Español (México) · Français (Canada) · Deutsch (Schweiz)

A park seen from above: paths at right angles, and one diagonal shortcut that people kept walking, now deliberately paved, with the old worn track still faint beside it.
圖片由 AI 生成

第 5 個模組,共 6 個,屬於 Data Architect 課程第 2 章為含義建模資料。每個模組都可以單獨閱讀。 上一篇:把資料放在該放的地方 · 下一篇:把模型落到 Salesforce 中

**本模組回答的問題:**什麼時候我應該有意偏離一個乾淨的規範化模型?

在上一個模組裡,我告訴你要按第三範式來設計。

現在我要告訴你打破它。

有時候。

這個區別很重要。無意中構建出一個雜亂的資料模型,和有意地對一個乾淨的模型做反規範化,差別很大。如果你一開始就不知道資料屬於哪裡,把它複製到別處就不是架構,只是重複。

但一旦理解了乾淨的邏輯模型,我就可以有意識地決定,為消費資料的人和系統把資料塑造成不同的形狀。

在可行的情況下設計到第三範式。然後在必要時適度回退。

注意這句話沒有說什麼。它沒有說第三範式更容易理解。有時它更難:一個完全規範化的模型可能把一個業務概念分散到五個物件裡,而使用它的人得在腦子裡把它重新拼起來。規範化告訴我每個事實屬於哪裡。它不保證那是最容易閱讀、使用或做報表的形狀。這正是為什麼適度回退是一種設計技能,而不是一種失敗。

先為真相建模。再決定這個真相需要如何交付。

回到狗,這一次我們要做一些有意的選擇。

**品種:挑選清單,還是獨立的表?**用我在本模組後面會給你的那些問題過一遍。資訊是不是不止一列?是的:至少還有體型。有多少個取值?各家犬業俱樂部認可的品種有幾百個(光是美國養犬俱樂部 American Kennel Club 就列出了 205 個),遠遠超過我大約 20 個的經驗法則。所以品種值得擁有自己的表,每隻狗都指向它的品種。這就解決了我們從模組 1 一直帶著的問題:品種是一個獨立的方框。

**熟練程度:挑選清單。**學習中、良好、精通(Learning、Good、Expert)。三個標籤,關於它們沒有別的可說,而且不會變。這正是挑選清單的用途。

每隻狗會多少項技能?答案已經在模型裡了:數一數這隻狗的狗狗技能記錄。但訓練師希望每份報表上都有這個數字,可以按它排序、按它篩選,而不必跨兩張表去建報表。所以我適度回退,在狗身上直接保留一個已會技能數(Tricks Known)。這是一份副本。真相仍然存在於狗狗技能中;這個計數只是它的一種呈現,必須與之保持同步。Salesforce 給了我們一個乾淨的辦法來做到這一點,我們會在模組 6 看到。

現在來個轉折:狗狗學校開始收貓了。我們怎麼辦?一個選項:在狗的表旁邊加一張貓的表,在狗狗技能旁邊加一個貓咪技能。一切都翻倍:每個介面兩份,每份報表兩份,等到哪天開始收兔子,就是三份。另一個選項:退一步,注意到一隻貓有主人,也許有繁育者,有品種、出生日期,耐心一點的話,還會一兩項技能。和狗一樣的事實,一樣的關係。所以我們做泛化:一個寵物(Pet),帶一個物種(Species),狗或貓。每個品種也屬於某個物種,那才是物種真正所在的地方:拉布拉多是狗。寵物上有意帶著一份副本,這樣每個介面和每份報表都可以先問「狗還是貓?」,而不必去查品種。又一份有意為之的副本,到模組 6 它會有自己的用處。

這就是為變化而設計,而且這是一種權衡。泛化得太早,一切都會變成一個含糊的「事物」(Thing),帶著一個誰也看不懂的類型欄位。只有當業務真的給我帶來第二種東西,而且兩者幾乎所有方面都相同,就像這裡的狗和貓一樣,我才做泛化。如果貓需要非常不同的事實,我會三思。

經過權衡後的寵物模型 作為詳細資料模型的寵物模型。寵物取代了狗的位置:寵物 ID(還以晶片號為已知標識)、主人 ID、繁育者 ID 和品種 ID、物種(挑選清單:狗或貓)、名字、出生日期,以及為報表保留的已會技能數。品種現已連線(一隻寵物屬於一個品種),並有自己的物種、名稱和體型。寵物技能位於寵物和技能之間,熟練程度為挑選清單,另有學會日期;技能評估在它下方,帶有日期、熟練程度和評估人。主人擁有寵物,繁育者繁育寵物,和之前一樣。 擁有 繁育 屬於 會 見於 受檢於 主人 主人 ID 記錄 ID 姓名 電話 ZIP 城市 繁育者 繁育者 ID 記錄 ID 名稱 城市 寵物 寵物 ID 記錄 ID 晶片號 又稱 主人 ID 繁育者 ID 品種 ID 物種(挑選清單) 名字 出生日期 已會技能數(計數) 品種 品種 ID 記錄 ID 物種(挑選清單) 名稱 體型 寵物技能 寵物技能 ID 記錄 ID 寵物 ID 技能 ID 熟練程度(挑選清單) 學會日期 技能 技能 ID 記錄 ID 名稱 難度 技能評估 技能評估 ID 記錄 ID 寵物技能 ID 日期 熟練程度(挑選清單) 評估人 恰好一個 零個或一個 一個或多個 零個或多個 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

現在到 Steve Trucking 做同樣類型的選擇,那裡的利害關係更大。

最乾淨的模型不一定是最好的系統

Section titled “最乾淨的模型不一定是最好的系統 ”

想像一個配送小組在一個停靠點。配送有它的項目。安裝有一份檢查清單。每一步都需要照片。收貨人要簽字。

規範化的模型很漂亮:配送 → 配送項目 → 安裝檢查清單 → 照片,每樣都在該在的地方。

然後我和真正使用它的人坐下來。一個配送小組每天要完成大約十個停靠點,在人家的車道上,常常沒有訊號。如果我漂亮的模型讓他們穿過一個由各種介面和相關清單組成的迷宮才能完成一個停靠點,那我就有一個架構問題。

資料模型並不是我們唯一在設計的東西。

我們在設計一個系統。

這是我有時會偏離一個完全規範化模型的原因之一:使用者體驗。

也許某個概念在技術上可以成為自己的實體,但把它分出來增加的複雜度幾乎沒帶來什麼價值。也許一個帶有好幾層子記錄的父記錄在學術上很乾淨,但錄入和維護起來很痛苦。也許這個概念很小、很穩定,主要被用作一個選擇項。

在這裡,我不再只問**「這個事實理論上屬於哪裡?」,而是開始問「這個系統實際上會被怎樣使用?」**

這並不意味著每一句關於多點一下的抱怨都要把模型做扁平。它意味著可用性是一項正當的架構輸入。

報表是另一個常見原因。

每個零售合作夥伴都想要一份記分卡:按區域、按月份統計的準時配送情況。在乾淨的模型裡,一次配送知道它的配送中心,配送中心知道它的區域。技術上,我們可以沿著這條路徑找過去。

但報表工具能嗎?自己做報表的人能嗎?他們能不能不借助一套比問題本身還複雜的報表架構就回答這個問題?

這時我可能會把區域複製或推導到配送上,離它被使用的地方更近一些。

現在我引入了重複。所以我需要另一個問題:誰擁有真相?

如果源值變了,我的副本怎麼辦?它應該跟著變嗎?還是應該保留配送發生時為真的那個值?這是不同的需求。

反規範化並不會消除複雜度。

它只是轉移了複雜度。

這裡有一個 Salesforce 管理員經常遇到的小例子。使用者需要從一組值中選擇。我是再建立一個實體並與之關聯,還是用一個挑選清單?

拿配送的狀態來說:已排期、已裝車、配送中、即將到達、已送達、已安裝、異常、已退回。一份短小、穩定的標籤清單。挑選清單正是我想要的。

再看停靠點的類型:僅配送、配送加安裝、配送加拉走舊貨。這個就帶有自己的資訊:停靠點要花多長時間,配送小組需要什麼技能,適用哪份安裝檢查清單。它獨立變化,由業務方管理。這是一個實體,不是一個標籤。

如果我為每一個理論上可以規範化的概念都建立一個物件,我就可能構建出一個技術上很漂亮、卻沒人用得開心的系統。如果我因為省事把每個概念都做成挑選清單,我很快就會把自己逼進死角。

所以我會問:它有自己的資訊嗎?有關係嗎?它會獨立變化嗎?它需要作為資料來管理嗎?業務方將來是否可能需要它提供不止一個標籤的東西?

下面是我在設計會議上實際會問的問題。

**資訊是否不止一列?**如果這個值需要一個描述、一個時長、一份技能清單、一個負責人,它就不再是一個標籤了。

**有多少個值?**我的經驗法則是大約 20 個。超過 20 個值,挑選清單就開始不好用了,是時候考慮用物件了。美國各州有五十個,是我們忍著用的例外,國家就更糟了;說實話,有誰喜歡國家挑選清單嗎?我覺得它很煩人。

**這些值是否會隨時間不斷新增,由誰新增?**新增挑選清單的值是一次配置變更,這意味著要找管理員。如果業務方每週都在新增值,而新增的人又不是管理員,挑選清單就會讓每一個新值都得先過一張工單。

**人們需要按它篩選和做報表嗎?**挑選清單在這方面很出色。查閱關係就沒那麼幹淨了。每個查閱關係都會多一跳:報表類型只能沿著一條很短的相關物件鏈走下去(自訂報表類型最多允許四層物件關係),介面會變成相關清單套相關清單,程式碼也會更復雜。

一份值清單往往一開始是挑選清單,隨著時間推移,**它會掙得一個獨立的物件。**讓它自己去掙。

標籤還是實體?狀態與停靠點類型 兩張卡片。配送狀態,一個挑選清單:八個穩定的值,每個都是一個英文單字或帶連字號的單字(Scheduled、Loaded、Out-for-Delivery、Arriving、Delivered、Installed、Exception、Returned),每個都只是一個標籤。停靠點類型,一個實體:二十四個值,還在增加,例如僅配送、配送加安裝洗衣機或瓦斯爐、配送加舊機拉走、僅舊機拉走、退貨取件,以及另外十八種;每個值都有自己的停靠時長、所需配送小組技能和安裝檢查清單。下方的註記:超過大約 20 個值,或有自己的屬性,它就是實體,而不是標籤。 配送狀態 一個挑選清單 值:8 個,而且穩定 Scheduled Loaded Out-for-Delivery Arriving Delivered Installed Exception Returned 每個值都是 只是一個標籤 停靠點類型 一個實體 值:24 個,還在增加 僅配送 配送 + 安裝:洗衣機 配送 + 安裝:瓦斯爐 配送 + 舊機拉走 僅舊機拉走 退貨取件 ……還有 18 種 每個值都有自己的 停靠時長 所需配送小組技能 安裝檢查清單 超過大約 20 個值,或有自己的屬性:它就是實體,而不是標籤。 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

一個在幾千條記錄下執行得很漂亮的設計,在幾百萬條記錄下可能表現得截然不同。

所以不要只看圖。要了解數字。

Steve Trucking 每年大約有一百萬次配送,每年增長 30%。每次配送大約收集八次狀態更新。今年就是八百萬條狀態記錄,到第三年大約每年 1,350 萬條。再加上每年四五百萬張照片。

同樣的模型,截然不同的架構。

我們會在 Architecting at Scale 中深入得多。現在請記住:**資料量可以改變一個數據建模的決定。**一個邏輯上正確的關係,並不會自動成為每種規模下最好的物理設計。

到目前為止,我們一直在考慮運營系統。排程員給配送排路線。配送小組完成停靠點。系統隨著業務運轉而變化。

但業務方也想分析多年的歷史。哪些合作夥伴和區域經常延誤?配送時間有什麼變化?安裝需要二次上門的頻率有多高?

一個分析系統可能希望同樣的資訊以不同的方式組織。你會聽到資料倉儲、事實、維度和星型模式這些術語。我們在這裡不展開;在 Steve Trucking,那就是 Snowflake 資料倉儲,我們會在課程後面回到它。

現在只要記住:不同的消費者可能需要同一個真相的不同形狀。

而現在又多了一個消費者:AI。

一位收貨人發簡訊問:「我的貨到哪兒了?」一個 AI 助手可能會查到這個:

配送 10427,路線 3312,停靠點 7,卡車 117,時間視窗 13:00–15:00。

傳統應用很樂意沿著這些關係走下去。但要回答收貨人,助手需要從好幾個地方拼出上下文:

你的 Redrock 洗碗機是今天從鳳凰城配送中心出發的路線上的第七個停靠點。卡車還有三站就到,仍在你下午 1 點到 3 點的時間視窗內。

我們從好幾個相關實體中取出資訊,為某個特定的消費者拼出了一個更扁平、上下文更豐富的表示。

這並不意味著我要毀掉我的規範化模型。更不意味著「AI 喜歡扁平的資料,所以把所有東西都放進一個巨大的物件裡」。不要那樣做。

真相的來源保持乾淨。AI 得到的是一個為它所做的工作而構建的表示。這和我們剛剛在分析中看到的是同一個思路:

一個真相來源可以支撐多種用於消費的形狀。

我們會在課程後面深入討論落地(grounding)和檢索。

這裡有一個危險。

如果我為了報表把區域複製到配送上,給 Snowflake 發一個扁平化的版本,再為 AI 助手構建一個上下文豐富的版本,哪一個才是正確的?

這個問題需要一個答案。我想知道權威的值在哪裡。其他的一切都是這個真相為了某個目的而做的表示。

那個地方就是記錄系統。反規範化的副本可以是好東西:停靠點介面、記分卡、Snowflake 和 AI 上下文,都因為有了為它們構建的形狀而把工作做得更好,引用它們也沒問題。但當有東西變化時,變化從記錄系統開始,通常就是關係模型,然後向外流向每一個副本。沒有人去 Snowflake 裡修正區域,也沒有人手動編輯 AI 的上下文。在源頭修正,讓變化流出去。

記錄系統:變化向外流動 上方一個方框是記錄系統,關係型,變化從這裡開始,箭頭向下流向由它建置的四個副本:配送小組停靠點介面,一個停靠點的一切都在一頁上;合作夥伴記分卡,按區域統計延遲配送;Snowflake 資料倉儲,存有多年的歷史;以及 AI 配送上下文,用來回答「我的貨到哪兒了?」。下方的註記:變化從記錄系統開始,向外流向每一個副本;絕不修正副本。 記錄系統 關係型:變化從這裡開始 配送小組停靠點介面 一個停靠點,全在一頁 合作夥伴記分卡 按區域統計延遲配送 Snowflake 資料倉儲 多年的歷史 AI 配送上下文 「我的貨到哪兒了?」 變化從記錄系統開始,向外流向每一個副本。絕不修正副本。 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

而當資料變化時,這一點最為重要。如果某個配送中心今天劃到了另一個區域,上個月的配送應該顯示新區域嗎?也許應該,也許不應該。對於歷史,我可能需要的是配送發生時為真的那個區域。這就不再是一個同步問題了,而是一個業務決定。

這就是為什麼反規範化絕不應該是意外發生的。每一個被重複的事實,都會帶來關於所有權、同步、歷史和信任的問題。這是第 1 章裡的權衡思維:知道你買到了什麼(更簡單的介面、更簡單的報表、給 AI 的更好上下文),以及你付出了什麼(重複的資料、同步、更復雜的真相所有權)。而且因為我們是從一個乾淨的邏輯模型出發的,我們可以指著每一個副本說:**「我知道它屬於那邊。因為這項需求,我在這裡放了它的一個表示。」**這是一個我們可以評審、質疑並寫下來的決定,這樣下一位架構師就知道為什麼。

在考慮反規範化時,我要決定的是:

  • 每個業務事實的權威來源在哪裡。
  • 乾淨的模型是否給使用者、報表、規模或效能帶來了真正的問題。
  • 一個更簡單的表示是否值得付出重複的代價,以及副本如何保持可信。
  • 噹噹前的來源變化時,歷史值是否應該跟著變。
  • 分析或 AI 是否需要與運營系統不同的資料形狀。

起點永遠不變:知道真相屬於哪裡。然後,當需求證明有必要時,為交付去塑造它。

接下來,Salesforce 重新回到畫面中。我們要把設計好的東西變成一個物理資料模型。