~/VibeHandbook
免费 PDF

14 · 03

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 NULLUNIQUE 约束。这些是数据库为你强制执行的护栏,所以即使你的代码有 bug,坏数据也无法溜进来。

一个值得有意添加的约束,是当父记录消失时会发生什么。默认情况下,删除一个仍被帖子引用的用户会失败——而这往往正是你想要的。但你可以显式指定:REFERENCES users(id) ON DELETE CASCADE 会把帖子也一并删除,而 ON DELETE RESTRICT 会阻止该删除。有意去选,胜过在生产环境里以惨痛方式发现默认行为。同样的"让数据库保护你"的直觉也适用于 CHECK 约束(CHECK (price >= 0))——它在源头拒绝荒谬的数据,而不是去信任每一条代码路径都会做校验。

想离线阅读?

免费下载整本书的 PDF 或 EPUB。