mcpField
D
database-schema-design

database-schema-design

github.com/mohitagw15856数据与数据库1.5k50 次安装1 周前更新
代码仓库

关于 database-schema-design

database-schema-design 是用于编写数据库模式设计文档的 Skill,面向工程师和负责数据建模的团队。它不仅描述表结构,还记录实体关系、查询模式、索引策略及设计取舍。

主要功能

  1. 01整理领域实体与关系
  2. 02生成表定义和关系图
  3. 03按查询模式设计索引
  4. 04分析访问模式与规范化
  5. 05记录迁移及自动化决策

适用场景

  1. 01为新服务设计数据库模式
  2. 02为常用查询规划索引
  3. 03编写可供工程师评审的设计文档

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mohitagw15856/pm-claude-skills --skill database-schema-design

安装后在 Claude Code 中输入/database-schema-design确认已加载。

详细介绍

数据库架构设计技能

为给定领域生成一份完整的数据库架构设计文档。架构文档不只是表的列表——它记录了设计决策:建模了什么、实体之间如何关联、架构针对哪些查询进行了优化,以及做出了哪些权衡。

优秀的架构设计文档能帮助工程师理解数据模型、正确查询数据、安全扩展架构,并编写不会造成破坏的迁移。

必需输入

如果尚未提供,请询问以下信息:

  • 领域描述——系统的功能;要建模哪些业务对象
  • 实体与关系——领域中的主要事物及其相互关系(例如:“一个 User 有多个 Orders;一个 Order 有多个 OrderItems;一个 OrderItem 引用一个 Product”)
  • 预期查询模式——最重要的读写查询(例如:“按日期排序,获取某个用户的所有订单”;“按 SKU 查找商品”)
  • 数据库引擎——PostgreSQL、MySQL、SQLite、CockroachDB 等——这会影响 DDL 语法和可用类型
  • 预期数据量——大致行数、增长率,以及任何分区需求
  • 约束——需要遵循的现有惯例、命名标准或迁移约束

输出格式


数据库架构设计:[领域 / 服务名称]

服务:[名称] | 团队:[团队名称] 作者:[姓名] | 审核人:[姓名] 日期:[日期] | 数据库引擎:[PostgreSQL X.X / MySQL X.X / 等] 状态:[草稿 / 已审核 / 已批准]


1. 概述

[用 2–3 句话描述所建模的领域、本架构的范围,以及关键设计理念(例如:“本架构优先保障面向客户的 API 的读取性能,而非写入简便性”,或“设计时考虑了未来迁移到多租户架构的可能性”)]

范围内:

  • [实体或子系统]
  • [实体或子系统]

范围外:

  • [例如:分析 / 报表表——使用单独的架构]
  • [例如:审计日志表——在单独的设计文档中说明]

2. 实体关系图

┌───────────────────┐         ┌───────────────────────┐
│      users        │         │       organisations    │
│─────────────────  │         │─────────────────────── │
│ id (PK)           │    ┌───▶│ id (PK)                │
│ org_id (FK)  ─────┼────┘    │ name                   │
│ email             │         │ plan                   │
│ display_name      │         │ created_at             │
│ created_at        │         └───────────────────────┘
│ updated_at        │
└─────────┬─────────┘
          │ 1
          │
          │ N
┌─────────▼─────────┐         ┌───────────────────────┐
│      [table_a]    │         │      [table_b]         │
│─────────────────  │         │─────────────────────── │
│ id (PK)           │    N    │ id (PK)                │
│ user_id (FK) ─────┼────────▶│ [table_a]_id (FK)      │
│ [field]           │    │    │ [field]                │
│ [field]           │    │    │ [field]                │
│ created_at        │         │ created_at             │
└───────────────────┘         └───────────────────────┘

关系摘要:

实体 A关系实体 B备注
organisations有多个users一个组织可以有多个用户
users有多个[table_a]用户删除时软删除
[table_a]有多个[table_b]级联删除
[table_b]属于[table_a]不可为空的外键
[table_c]多对多(通过 [join_table])[table_d]带有元数据的联结表

3. 表定义

organisations

[用 1 句话说明此表存储的内容及其在领域中的作用。]

CREATE TABLE organisations (
    id          UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    name        VARCHAR(255)    NOT NULL,
    slug        VARCHAR(100)    NOT NULL UNIQUE,
    plan        VARCHAR(50)     NOT NULL DEFAULT 'free'
                                CHECK (plan IN ('free', 'pro', 'enterprise')),
    settings    JSONB           NOT NULL DEFAULT '{}',
    created_at  TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ     NOT NULL DEFAULT now()
);
列类型可为空默认值备注
idUUID否gen_random_uuid()代理主键(PK)——分布式使用时优先采用 UUID,而非 serial
nameVARCHAR(255)否—显示名称;不唯一
slugVARCHAR(100)否—URL 安全标识符;在所有组织中唯一
planVARCHAR(50)否'free'通过 CHECK 限定为已知值
settingsJSONB否{}灵活配置;避免用于可查询字段
created_atTIMESTAMPTZ否now()始终使用 TIMESTAMPTZ,而非 TIMESTAMP
updated_atTIMESTAMPTZ否now()通过触发器更新(见下文)

users

[用 1 句话说明此表存储的内容。]

CREATE TABLE users (
    id              UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    org_id          UUID            NOT NULL REFERENCES organisations(id)
                                    ON DELETE RESTRICT,
    email           VARCHAR(254)    NOT NULL,
    display_name    VARCHAR(255)    NOT NULL DEFAULT '',
    role            VARCHAR(50)     NOT NULL DEFAULT 'member'
                                    CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
    email_verified  BOOLEAN         NOT NULL DEFAULT false,
    deleted_at      TIMESTAMPTZ     NULL,
    created_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ     NOT NULL DEFAULT now(),

    CONSTRAINT users_email_org_unique UNIQUE (email, org_id)
);
列类型可为空默认值备注
idUUID否gen_random_uuid()—
org_idUUID否—organisations 的外键;RESTRICT 可防止产生孤立记录
emailVARCHAR(254)否—RFC 5321 规定的最大长度;在每个组织内唯一(非全局唯一)
roleVARCHAR(50)否'member'应用层 RBAC
deleted_atTIMESTAMPTZ是NULL软删除;NULL = 活跃

软删除策略:deleted_at IS NOT NULL 的行视为已删除。除非明确要获取已删除记录,否则所有应用查询都必须通过 WHERE deleted_at IS NULL 进行筛选。使用视图或 ORM 作用域来强制执行此规则。


[table_a]

[对此表建模内容的描述。]

CREATE TABLE [table_a] (
    id          UUID            PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id     UUID            NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    [field_1]   VARCHAR(255)    NOT NULL,
    [field_2]   TEXT            NULL,
    [field_3]   INTEGER         NOT NULL DEFAULT 0 CHECK ([field_3] >= 0),
    status      VARCHAR(50)     NOT NULL DEFAULT 'pending'
                                CHECK (status IN ('pending', 'active', 'archived')),
    metadata    JSONB           NOT NULL DEFAULT '{}',
    created_at  TIMESTAMPTZ     NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ     NOT NULL DEFAULT now()
);
列类型可为空备注
user_idUUID否级联删除——删除用户时,其对应的 [table_a] 行也会删除
[field_1]VARCHAR(255)否[长度限制的原因]
statusVARCHAR(50)否状态机:pending → active → archived(不允许其他转换)
metadataJSONB否[此处存储的内容,以及不将其设为类型化列的原因]

[join_table] (多对多)

[此表所表示关系的描述。]

CREATE TABLE [join_table] (
    [table_c]_id    UUID        NOT NULL REFERENCES [table_c](id) ON DELETE CASCADE,
    [table_d]_id    UUID        NOT NULL REFERENCES [table_d](id) ON DELETE CASCADE,
    granted_by      UUID        NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
    granted_at      TIMESTAMPTZ NOT NULL DEFAULT now(),

    PRIMARY KEY ([table_c]_id, [table_d]_id)
);

采用复合主键的原因: [table_c]_id + [table_d]_id 的组合是自然键——每个关联都是唯一的,主键同时充当唯一性约束,无需单独的索引。


4. 索引策略

针对每张表,定义要创建哪些索引及其原因。包括这些索引要服务的查询。

表索引名称列类型服务的查询备注
usersusers_org_id_idx(org_id)B-treeSELECT * FROM users WHERE org_id = $1外键查找;提升连接性能所必需
usersusers_email_lower_idx(lower(email))B-tree(函数索引)WHERE lower(email) = lower($1)不区分大小写的电子邮件查找
usersusers_active_by_org_idx(org_id, created_at DESC)B-treeWHERE org_id = $1 AND deleted_at IS NULL ORDER BY created_at DESC可考虑使用部分索引(见下文)
[table_a][table_a]_user_id_status_idx(user_id, status)B-treeWHERE user_id = $1 AND status = 'active'复合索引——列的顺序很重要
[table_a][table_a]_metadata_gin_idxmetadataGINWHERE metadata @> '{"key": "value"}'仅在频繁查询 JSONB 时添加

部分索引(PostgreSQL):

-- Index only active (non-deleted) users — dramatically smaller for soft-delete tables
CREATE INDEX users_active_email_idx
    ON users (email, org_id)
    WHERE deleted_at IS NULL;

-- Index only pending items — avoids indexing the majority of rows
CREATE INDEX [table_a]_pending_idx
    ON [table_a] (user_id, created_at)
    WHERE status = 'pending';

所应用的索引设计原则:

  • 出现在 JOIN 条件中的外键始终建立索引
  • 复合索引按选择性排序:选择性最高的列放在最前
  • 对不区分大小写的查找使用函数索引
  • 仅在频繁执行 JSONB 包含查询时使用 GIN 索引
  • 对大型表中按状态筛选的查询使用部分索引

5. 访问模式分析

记录此架构旨在支持的主要查询。针对每种查询,展示查询语句、使用的索引及注意事项。

AP-1:获取某个组织的所有活跃用户(分页)

频率: 很高——每次加载仪表板时都会调用
查询:

SELECT id, email, display_name, role, created_at
FROM users
WHERE org_id = $1
  AND deleted_at IS NULL
ORDER BY created_at DESC
LIMIT 50 OFFSET $2;

使用的索引: users_active_by_org_idx(org_id, created_at DESC)
备注: 数据量较大时使用键集分页(WHERE created_at < $cursor);行数超过约 10k 后,OFFSET 的性能会下降。


AP-2:按电子邮件查找用户(不区分大小写)

频率: 高——每次身份验证尝试都会调用
查询:

SELECT id, org_id, role, email_verified
FROM users
WHERE lower(email) = lower($1)
  AND deleted_at IS NULL;

使用的索引: users_email_lower_idx
备注: 如果不同组织中存在相同的电子邮件地址,则会返回多行。应用程序根据组织上下文进行处理。


AP-3:按状态获取用户的 [table_a] 项目

频率: 高
查询:

SELECT *
FROM [table_a]
WHERE user_id = $1
  AND status = $2
ORDER BY created_at DESC
LIMIT 25;

使用的索引: [table_a]_user_id_status_idx
备注: 复合索引覆盖两个筛选列。由于 user_id 的选择性更高,因此 status 必须在索引中排在第二位。


AP-4:[根据需要添加其他访问模式]

6. 规范化决策

记录有意进行规范化或反规范化的选择,并说明理由。

决策方案理由
[例如:是否在用户表中存储组织名称?]不进行反规范化 — 始终联接到组织表避免副本过时;组织名称很少更改,且联接成本很低
[例如:状态历史记录]不放在此表中 — 如有需要,单独建立 [table_a]_status_history99% 的查询只需要当前状态;历史记录用于审计,而非应用数据
[例如:组织表上的 JSONB settings 列]反规范化为 JSONB设置总是一起读取;从不按字段查询;更改架构无需迁移
[例如:计算得出的聚合计数]不存储 — 在查询时计算计数较小;维护计数器列需要谨慎处理锁;使用带索引的 SELECT COUNT(*)

7. 触发器和自动化

-- Automatically update updated_at on any row modification
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = now();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Apply to all tables with updated_at
CREATE TRIGGER users_updated_at
    BEFORE UPDATE ON users
    FOR EACH ROW EXECUTE FUNCTION set_updated_at();

CREATE TRIGGER [table_a]_updated_at
    BEFORE UPDATE ON [table_a]
    FOR EACH ROW EXECUTE FUNCTION set_updated_at();

8. 迁移说明

如果要将此架构引入现有系统,请说明迁移方案。

步骤描述向后兼容风险
1创建 organisations 表是 — 增量变更低
2创建 users 表是 — 增量变更低
3为现有用户回填 org_id需要经过双写阶段中
4为 org_id 添加 NOT NULL 约束需要 100% 完成回填中
5移除已弃用的列需要先更新应用代码应用部署完成后风险低

回填策略: [说明如何处理现有数据 — 批次大小、速率限制、验证查询]

回滚: 每个迁移步骤都应能够单独回滚。完整的回滚流程模板请参阅 [database-migration-plan skill]。


质量检查

  • 每张表都有主键和 created_at 列 — 不依赖按行插入顺序进行隐式排序
  • 每个外键都有对应的索引 — 不遗漏 FK 索引,以免联接时导致全表扫描
  • 所有时间戳列都使用 TIMESTAMPTZ,而非 TIMESTAMP — 明确处理时区
  • 软删除表需记录约定,以及筛选条件的执行位置(ORM 作用域、视图或查询规范)
  • 设计中的每种访问模式都有相应索引,或明确说明可接受全表扫描
  • JSONB 列有充分理由 — 不将其用作可查询字段的规范架构设计的替代方案
  • 记录规范化决策及其理由,而非仅列出决策
  • 如果是架构变更而非从零开始设计,迁移说明需涵盖现有数据

反模式

  • 不要将 JSONB 列用作会被查询的字段的规范关系架构设计的替代方案
  • 不要随意添加索引 — 每个索引都必须有特定访问模式作为依据
  • 不要忽略时区处理 — 使用 TIMESTAMPTZ,绝不使用普通 TIMESTAMP
  • 不要在未记录规范化决策的情况下进行设计 — 后续维护者需要了解理由,而不仅是结构
  • 不要跳过访问模式部分 — 缺少查询模式的架构无法评估其正确性

示例提示语

  • “设计一个数据库。”
  • “记录现有架构。”
  • “对实体及其关系建模。”
  • “定义表结构。”
  • “规划索引策略。”

评论

支持 Markdown