Schema 設計基礎
schema 是你資料的藍圖:存在哪些表、它們有哪些列,以及它們如何關聯。好的 schema 設計大多是常識加上幾個習慣。
- 每一種"東西"對應一張表。 使用者、帖子、訂單——各自擁有自己的表。
- 每一行都需要一個唯一的 ID(一個主鍵)。
- 用外部索引鍵連結表,而不是到處複製資料。
- 選擇誠實的型別。 金錢是
decimal,不是float。日期是時間戳,不是文本。 - 不要重複。 如果一個使用者的郵箱存在於五張表裡,你最終會有五個不同的郵箱。
下面是一個部落格的簡單 schema,正是 AI 會為你生成的那種:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
author_id UUID NOT NULL REFERENCES users(id),
title TEXT NOT NULL,
body TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_posts_author ON posts(author_id);
author_id ... REFERENCES users(id) 這一行就是外部索引鍵。它把每篇帖子接回到恰好一個真實使用者,於是資料庫自身就會拒絕儲存一篇沒有有效作者的帖子:
users posts
┌─────────────┐ ┌─────────────┐
│ id (PK) ◀──┼────────────────┤ author_id │ (FK → users.id)
│ email │ 一個使用者 │ id (PK) │
│ created_at │ 擁有多篇 │ title │
└─────────────┘ 帖子 │ body │
│ published │
1 ────────────────────▶ *└─────────────┘
(一) (多)
注意 REFERENCES(一個把帖子繫結到真實使用者的外部索引鍵)以及 NOT NULL 和 UNIQUE 約束。這些是資料庫為你強制執行的護欄,所以即使你的程式碼有 bug,壞資料也無法溜進來。
一個值得有意新增的約束,是當父記錄消失時會發生什麼。預設情況下,刪除一個仍被帖子引用的使用者會失敗——而這往往正是你想要的。但你可以顯式指定:REFERENCES users(id) ON DELETE CASCADE 會把帖子也一併刪除,而 ON DELETE RESTRICT 會阻止該刪除。有意去選,勝過在生產環境裡以慘痛方式發現預設行為。同樣的"讓資料庫保護你"的直覺也適用於 CHECK 約束(CHECK (price >= 0))——它在源頭拒絕荒謬的資料,而不是去信任每一條程式碼路徑都會做校驗。