Home
Explore Uedu
Student Console
Register as Member/Login
(2) In future presentations of the research findings, in addition to the course project website and public presentations, your real name and personal information will not appear in this research report. If you are interested in the research results, we can provide you with an executive summary after the study is completed.
問卷中心
Teacher Console
Course Setup
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

0

Local AI

--

CISOSE26 本地 AI UG26
政治大學 AQI 18 29°C

AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

資料模型與正規化

資料模型與正規化

從一個學生地址裂成三個版本的災難談起,用 ER 模型、函數相依與 1NF/2NF/3NF,讓每個事實只住在一個地方。

同一個學生地址,為什麼系統裡有三個版本?

某間學校的選課系統出了狀況:行政人員想寄紙本通知給一位學生,卻發現資料庫裡同一個學生竟有三筆不同的住址,三筆裡兩筆是舊地址、一筆才是搬家後的新地址。追查之後才明白——這位學生選了三門課,每次選課時系統都把「學生住址」連同選課紀錄一起存了一份。後來他搬家,只更新了其中一處,於是同一個事實在資料庫裡分裂成互相矛盾的三個版本。

這不是程式有 bug,而是資料模型設計失當。當「同一個事實被儲存在多個地方」時,更新就注定會遺漏,矛盾只是時間問題。資料模型與正規化(normalization)這門學問,核心要回答的就是:該如何安排資料的結構,讓每個事實只存在一個地方,使矛盾在設計上就無法發生。

資料庫與資料管理概念示意圖

從現實世界到 ER 模型

在動手建表之前,我們需要一張藍圖,描述「這個世界裡有哪些東西、它們之間如何關聯」。這張藍圖最常用的工具是實體關係模型(Entity-Relationship Model,ER Model),由 Peter Chen 於 1976 年提出。

ER 模型有三個基本構件:

  • 實體(Entity):現實世界中可以被獨立辨識的事物。例如「學生」、「課程」、「教師」。實體的集合稱為實體集(entity set),落地到資料庫通常就是一張表。
  • 屬性(Attribute):描述實體的特徵。例如學生有「學號」、「姓名」、「住址」;課程有「課號」、「課名」、「學分」。其中能唯一辨識一個實體的屬性(或屬性組合)稱為鍵(key),例如學號。
  • 關係(Relationship):實體之間的關聯。例如「學生 選修 課程」、「教師 教授 課程」。

關係還有基數(cardinality)之分,描述兩邊各能對應多少個對象:

基數 意義 範例
一對一(1:1) 一邊最多對應另一邊一個 一個人對應一張身分證
一對多(1:N) 一邊可對應另一邊多個 一位導師帶多位學生
多對多(M:N) 兩邊都可對應多個 學生選課、課程被多人選

多對多關係無法直接塞進兩張表,必須拆出一張關聯表(junction table)來承載。以「選課」為例:

學生(student)            選課(enrollment)               課程(course)
+--------+----------+    +--------+---------+--------+   +--------+--------+
| 學號   | 姓名     |    | 學號   | 課號    | 成績   |   | 課號   | 課名   |
+--------+----------+    +--------+---------+--------+   +--------+--------+
| S001   | 王小明   |    | S001   | C101    | 88     |   | C101   | 微積分 |
| S002   | 李小華   |    | S001   | C102    | 92     |   | C102   | 線性代數|
+--------+----------+    | S002   | C101    | 75     |   +--------+--------+
                         +--------+---------+--------+

注意「成績」這個屬性既不屬於學生、也不屬於課程,而是屬於「某位學生修某門課」這件事——它天生就該住在關聯表裡。這正是 ER 模型的價值:它逼我們在畫表之前,先想清楚每個屬性「在語意上到底屬於誰」。

為什麼要避免重複:異常(anomaly)的三種面貌

回到開場的故事。如果我們把所有東西塞進一張大表,會長成這樣:

學號 姓名 住址 課號 課名 教師 成績
S001 王小明 桃園市A路 C101 微積分 張老師 88
S001 王小明 桃園市A路 C102 線性代數 陳老師 92
S002 李小華 新竹市B街 C101 微積分 張老師 75

這張「萬用表」看似方便,實則埋了三顆地雷,統稱更新異常(update anomalies)

  • 更新異常(update anomaly):王小明搬家,「住址」在兩列重複出現,若只改一列就產生矛盾——這正是開場的災難。
  • 插入異常(insertion anomaly):新開一門尚無人選的課,因為沒有對應的學號,這門課就無處可放(除非塞入一堆 NULL)。
  • 刪除異常(deletion anomaly):若李小華退選 C101,而他是唯一選 C101 的人,刪掉這列的同時,「C101 是微積分、由張老師教」這個事實也跟著消失了。

三種異常的根源都是同一件事:一張表混進了多個不同的「主題」。住址屬於學生,課名與教師屬於課程,成績屬於選課,硬擠在一起,事實就被迫重複。正規化的任務,就是有系統地把它們拆開。

函數相依:正規化的語意基石

要嚴謹地拆表,我們需要一個比直覺更精確的工具——函數相依(Functional Dependency,FD)

給定一張表的兩組屬性 $X$ 與 $Y$,若「$X$ 的值一旦確定,$Y$ 的值就被唯一決定」,我們說 $Y$ 函數相依於 $X$,記作:

$$X \rightarrow Y$$

讀作「$X$ 決定 $Y$」。在上面的萬用表中,可以辨識出這些相依:

$$\text{學號} \rightarrow \text{姓名, 住址}$$ $$\text{課號} \rightarrow \text{課名, 教師}$$ $$\text{學號, 課號} \rightarrow \text{成績}$$

最後一條的左側是兩個屬性的組合,意思是「要同時知道學號和課號,才能決定成績」。整張表真正的鍵(key)正是 $\{\text{學號}, \text{課號}\}$——能唯一決定整列的最小屬性組合。

函數相依不是憑空規定的,而是來自對現實世界語意的觀察:一個學號就只屬於一個人,所以必然決定唯一的姓名。設計者的責任,是把所有「應當成立」的相依正確地寫出來。一旦相依寫對了,正規化就從一門藝術變成可機械操作的程序。

三大正規化形式:1NF、2NF、3NF

正規化是一階一階收緊的過程,每一階都針對一類特定的問題。

第一正規化(1NF):原子性

1NF 要求每個欄位只存放單一不可分割的值,且不得有重複的欄位群。 違反 1NF 的典型寫法是把多個值塞進一格:

學號 選修課程
S001 微積分, 線性代數

「微積分, 線性代數」是一格塞兩值,無法直接用 SQL 查詢「誰選了線性代數」。修正方式是拆成多列,每列一個值。1NF 是後續一切的前提。

第二正規化(2NF):消除部分相依

2NF 要求在 1NF 的基礎上,每個非鍵屬性都必須相依於整個鍵,不能只相依於鍵的一部分。 這只在鍵由多個屬性組成時才有意義。

看萬用表中的「課名」:

$$\text{課號} \rightarrow \text{課名}$$

課名只需要課號就能決定,根本用不到學號。也就是說,課名只部分相依(partial dependency)於鍵 $\{\text{學號}, \text{課號}\}$ 的一部分。這就是課名在每一列重複出現的數學根源。修正方式是把「課程」獨立成一張表:

選課(enrollment)           課程(course)
+------+------+------+      +------+--------+--------+
| 學號 | 課號 | 成績 |      | 課號 | 課名   | 教師   |
+------+------+------+      +------+--------+--------+
| S001 | C101 | 88   |      | C101 | 微積分 | 張老師 |
| S001 | C102 | 92   |      | C102 | 線性代數| 陳老師 |
| S002 | C101 | 75   |      +------+--------+--------+
+------+------+------+

現在「成績」完整相依於 $\{\text{學號}, \text{課號}\}$,課程資訊只存一份。

第三正規化(3NF):消除遞移相依

3NF 要求在 2NF 的基礎上,非鍵屬性不能相依於另一個非鍵屬性。 這種「鍵 → 非鍵 → 非鍵」的鏈式相依稱為遞移相依(transitive dependency)

假設課程表多了「教師」與「教師研究室」:

$$\text{課號} \rightarrow \text{教師} \rightarrow \text{研究室}$$

研究室其實是由教師決定的,與課號只是間接相關。後果是:同一位老師教兩門課,他的研究室就在課程表裡重複兩次,老師換研究室時又會出現更新異常。修正方式是再拆一張「教師」表:

課程(course)              教師(teacher)
+------+--------+--------+ +--------+----------+
| 課號 | 課名   | 教師   | | 教師   | 研究室   |
+------+--------+--------+ +--------+----------+
| C101 | 微積分 | 張老師 | | 張老師 | 理學院301|
| C102 | 線性代數| 陳老師 | | 陳老師 | 理學院305|
+------+--------+--------+ +--------+----------+

一個常被引用的口訣總結了 3NF 的精神:每個非鍵屬性都必須相依於「鍵、整個鍵、且只相依於鍵」(the key, the whole key, and nothing but the key)——這三段分別正好對應 1NF(要有鍵與原子值)、2NF(整個鍵)、3NF(只有鍵)。

動手看一個例子:把一張壞表正規化到 3NF

讓我們完整走一遍。原始的扁平表:

| 訂單號 | 客戶ID | 客戶名 | 客戶城市 | 商品ID | 商品名 | 單價 | 數量 |

辨識函數相依:

$$\text{訂單號} \rightarrow \text{客戶ID}$$ $$\text{客戶ID} \rightarrow \text{客戶名, 客戶城市}$$ $$\text{商品ID} \rightarrow \text{商品名, 單價}$$ $$\text{訂單號, 商品ID} \rightarrow \text{數量}$$

逐步拆解:

  1. 鍵是 $\{\text{訂單號}, \text{商品ID}\}$。商品名、單價只靠商品 ID 決定 → 部分相依,違反 2NF。
  2. 客戶名、客戶城市透過「訂單號 → 客戶 ID → …」而來 → 遞移相依,違反 3NF。

拆成四張表:

訂單(order)              訂單明細(order_item)
+--------+--------+      +--------+--------+------+
| 訂單號 | 客戶ID |      | 訂單號 | 商品ID | 數量 |
+--------+--------+      +--------+--------+------+

客戶(customer)           商品(product)
+--------+------+--------+ +--------+--------+------+
| 客戶ID | 客戶名| 客戶城市| | 商品ID | 商品名 | 單價 |
+--------+------+--------+ +--------+--------+------+

現在每個事實只住在一個地方:改客戶城市只動 customer 一列,改商品單價只動 product 一列,再也沒有矛盾的縫隙。

反正規化:當「正確」要為「快」讓步

既然正規化這麼好,是否永遠拆得越細越好?並非如此。正規化把資料分散到多張表,查詢時往往需要多表合併(JOIN)才能組回完整資訊。在高流量的讀取場景下,一個查詢動輒 JOIN 五六張表,會成為效能瓶頸。

反正規化(denormalization) 是刻意在正規化的設計上「加回」一些冗餘,用空間與一致性的代價,換取讀取速度。常見手法包括:

  • 冗餘欄位:在訂單表直接存一份「客戶名」快照,省去每次 JOIN 客戶表。電商常這麼做,因為下單當下的客戶名本來就該被「凍結」。
  • 預先計算的彙總值(materialized aggregate):在文章表存一個 comment_count 欄位,而非每次都 COUNT(*) 整張留言表。
  • 物化視圖(materialized view):把複雜 JOIN 的結果預先算好存成實體表,定期刷新。

反正規化的核心是一筆清醒的權衡,而非偷懶:

面向 正規化 反正規化
寫入 快、單點更新 慢、需同步多份冗餘
讀取 慢、需 JOIN 快、資料就近
一致性 結構上保證 靠應用程式維護
儲存空間

關鍵原則是:先正規化,確認效能真的是瓶頸後,再針對性地、有意識地反正規化,並明確記錄「這份冗餘由誰負責同步」。 一旦反正規化,原本由資料庫結構自動保證的一致性,就轉嫁成程式設計師的責任——這份債務必須被看見,而不是不小心欠下的。

重點回顧

  • ER 模型用實體、屬性、關係描述世界,把多對多關係落地成關聯表;它逼我們在建表前先想清楚每個屬性語意上屬於誰。
  • 把多個主題塞進一張表會引發更新、插入、刪除三種異常,根源都是「同一事實被重複儲存」。
  • 函數相依 $X \rightarrow Y$ 是正規化的語意基石,讓拆表從直覺變成可機械操作的程序。
  • 1NF/2NF/3NF 逐階收緊:原子值 → 消除部分相依 → 消除遞移相依,口訣是「相依於鍵、整個鍵、且只相依於鍵」。
  • 反正規化用冗餘換讀取效能,是清醒的權衡,代價是一致性責任從資料庫轉移到應用程式。

深入探討(研究所視角)

正規化之所以能成為一門嚴謹的理論,是因為函數相依背後有一套完整的公理系統支撐,使我們能從一組已知相依,推導出所有邏輯上必然成立的相依。這套規則由 William Armstrong 於 1974 年提出,稱為 Armstrong 公理(Armstrong's axioms),包含三條基本規則($X, Y, Z$ 為屬性集合):

$$\text{自反律(Reflexivity):若 } Y \subseteq X \text{,則 } X \rightarrow Y$$ $$\text{增廣律(Augmentation):若 } X \rightarrow Y \text{,則 } XZ \rightarrow YZ$$ $$\text{遞移律(Transitivity):若 } X \rightarrow Y \text{ 且 } Y \rightarrow Z \text{,則 } X \rightarrow Z$$

這三條已被證明同時是健全的(sound)——只推得出真相依,與完備的(complete)——能推得出所有成立的相依。由它們可導出聯合律、分解律、偽遞移律等實用衍生規則。

公理系統的核心應用是計算屬性閉包(attribute closure) $X^{+}$,即「從 $X$ 出發,沿著所有函數相依能決定的全部屬性集合」。其演算法是反覆套用相依直到不再增長:

def closure(X, fds):
    """計算屬性集合 X 在函數相依集 fds 下的閉包 X+。
    fds 為 [(左側集合, 右側集合), ...]"""
    result = set(X)
    changed = True
    while changed:
        changed = False
        for left, right in fds:
            # 若某相依的左側已被涵蓋,則右側也必然成立
            if set(left) <= result and not set(right) <= result:
                result |= set(right)
                changed = True
    return result

閉包是判定一切的基礎工具:要檢查 $\{\text{學號},\text{課號}\}$ 是不是候選鍵(candidate key),只需確認它的閉包等於全部屬性,且移除任一屬性後就不再如此(最小性)。判斷某個相依 $X \rightarrow Y$ 是否成立,也只要檢查 $Y \subseteq X^{+}$。

值得指出的是,3NF 在某些病態情況下仍不夠嚴格,於是有更緊的 Boyce-Codd 正規化形式(BCNF),要求每條非平凡函數相依的左側都必須是超鍵(superkey)。但 BCNF 與另一個我們想保留的性質——保持相依分解(dependency-preserving decomposition)——有時無法兼得,這是資料庫理論裡一個深刻的張力:有些關係能無損(lossless)地拆到 BCNF,卻會在拆解過程中失去某些原本能由結構檢查的相依。更高階的 4NF、5NF 則進一步處理多值相依(multivalued dependency) 與連接相依,那是當「兩個彼此獨立的多值屬性」共存於一表時才會浮現的問題。

把視野再拉開,會發現正規化只是「一致性如何被保證」這個大命題的一個切面。它與資料庫的 ACID 交易性質(特別是 Consistency 與 Isolation)互補:正規化在靜態結構上消除矛盾的可能,交易機制則在動態並行中維護不變式(invariant)。而在 NoSQL 與分散式系統的世界裡,這個權衡又被重新洗牌——文件型資料庫常刻意嵌入(embed)冗餘文件以避免分散式 JOIN,本質上是把反正規化當作預設策略,再用 CAP 定理框架下的最終一致性(eventual consistency)去承接隨之而來的同步難題。理解了函數相依與正規化,你就握住了一把尺,能在任何資料系統裡丈量「這份冗餘,到底是設計,還是債務」。

AI 共讀助教正在陪你讀:資料模型與正規化
嗨!我是這篇文章的共讀助教,只根據〈資料模型與正規化〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。