跳轉至

建立 2026-10-03 更新 2026-10-03

關聯式資料庫核心觀念

關聯式資料庫(Relational Database)把資料存放在多張「資料表」中,並透過共同的欄位把資料表串起來。理解資料表、主鍵、外鍵、正規化、交易與索引這六個觀念,就能看懂絕大多數資料庫的設計,也能避免資料重複與不一致的問題。

資料表、列與欄

資料表(Table)就像一張試算表工作表,但結構更嚴格:

  • 欄(Column):定義資料的屬性與型別,例如 name TEXT、age INTEGER。每一欄只能放同一種型別的資料。
  • 列(Row):一筆完整的資料,例如「Alice,30 歲」。
  • 結構(Schema):資料表有哪些欄位、各欄位的型別與限制,事先定義好。
id name age
1 Alice 30
2 Bob 24

與試算表最大的不同是:資料庫會強制檢查資料是否符合結構,例如年齡欄位不能填入文字、必填欄位不能空白,因此資料品質比較有保障。

主鍵與外鍵

主鍵(Primary Key)

主鍵是唯一識別每一列的欄位,值不能重複也不能是空值(NULL)。最常見的做法是用自動遞增的整數 id。有了主鍵,「修改 id = 2 那位使用者」這類操作才不會改錯人。

外鍵(Foreign Key)

外鍵是指向另一張表主鍵的欄位,用來表示兩張表之間的關係。例如訂單表的 user_id 指向使用者表的 id:

CREATE TABLE orders (
    id      INTEGER PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id),
    amount  INTEGER NOT NULL
);

資料庫會檢查外鍵的值是否真的存在於 users 表中,避免出現「訂單屬於一個不存在的使用者」這類孤兒資料。

SQLite 預設不檢查外鍵

SQLite 需要在每次連線後執行 PRAGMA foreign_keys = ON; 才會強制檢查外鍵;PostgreSQL 與 MySQL(InnoDB)則預設啟用。

資料表之間的三種關係

關係 例子 設計方式
一對一 使用者 ↔ 個人檔案 其中一張表放外鍵,並加上 UNIQUE
一對多 使用者 → 多筆訂單 在「多」的那一方(訂單)放外鍵
多對多 學生 ↔ 課程 另建一張中介表(例如 enrollments),放兩個外鍵
erDiagram
    USERS ||--o{ ORDERS : "下訂"
    STUDENTS ||--o{ ENROLLMENTS : "選修"
    COURSES  ||--o{ ENROLLMENTS : "被選"

正規化:為什麼要拆表?

正規化(Normalization)是把資料拆到多張表、讓同一件事只記錄在一個地方的設計方法。如果把使用者姓名直接寫在每一筆訂單裡,當使用者改名時就得修改所有訂單,漏改一筆就會產生不一致。

實務上記住前三個正規化形式(Normal Form)的精神即可:

  1. 第一正規化(1NF):每一格只放一個值,不要在一格裡塞「A, B, C」這種清單。
  2. 第二正規化(2NF):非主鍵欄位必須依賴整個主鍵,而不是主鍵的一部分。
  3. 第三正規化(3NF):非主鍵欄位不能依賴其他非主鍵欄位,例如「郵遞區號 → 城市」應該拆成另一張表。

反正規化

正規化會增加 JOIN 的次數。報表或讀取量極大的系統,有時會刻意保留部分重複資料(反正規化,Denormalization)來換取查詢速度,但這應該是量測過效能之後的決定。

交易與 ACID

交易(Transaction)是一組必須「全部成功或全部取消」的操作。最經典的例子是轉帳:從 A 帳戶扣款與存入 B 帳戶必須同時成功,否則錢就憑空消失。

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;   -- 若中途出錯,改執行 ROLLBACK; 取消全部變更

關聯式資料庫用 ACID 四個特性保證交易可靠:

特性 英文 意義
原子性 Atomicity 交易內的操作全部完成或全部不做
一致性 Consistency 交易前後資料都符合所有規則(主鍵、外鍵、限制條件)
隔離性 Isolation 同時進行的交易互不干擾,看不到彼此未完成的變更
持久性 Durability 交易一旦 COMMIT,即使當機或斷電資料也不會遺失

這也是 Python 程式寫入資料後一定要呼叫 conn.commit() 的原因:沒有 commit,變更只存在於尚未完成的交易中。

索引:讓查詢變快

索引(Index)就像書末的索引頁。沒有索引時,資料庫必須逐列掃描整張表(Full Table Scan);建立索引後,可以直接跳到符合條件的位置。

CREATE INDEX idx_users_email ON users(email);
  • 該建索引的欄位:常出現在 WHERE、JOIN、ORDER BY 中的欄位。主鍵與 UNIQUE 欄位會自動建立索引。
  • 代價:索引會佔用空間,並讓 INSERT、UPDATE、DELETE 變慢,因為每次寫入都要同步更新索引。所以不是越多越好。
  • 檢查方式:在查詢前加上 EXPLAIN(SQLite 為 EXPLAIN QUERY PLAN),可以看到資料庫是否真的用到了索引。

推薦影音

Learn Database Normalization - 1NF 到 5NF(Decomplexify)

簡述:用大量實例一步一步示範如何把一張雜亂的表正規化,是 YouTube 上講解正規化最清楚的影片之一,對應本頁「正規化」一節。

Relational Database ACID Transactions(Hussein Nasser)

簡述:後端工程師 Hussein Nasser 以實際範例拆解 ACID 四個特性與隔離等級(Isolation Level)會遇到的讀取異常,適合想深入理解「交易」的讀者。

Database Indexing Explained(Hussein Nasser)

簡述:在 PostgreSQL 上實際建立索引並比較查詢計畫(Query Plan),直觀看見索引帶來的速度差異,對應本頁「索引」一節。

相關資料