数据库 Schema 设计器
设计生产级数据库 Schema,内置最佳实践。
快速开始
只需描述你的数据模型:
design a schema for an e-commerce platform with users, products, orders
你将获得一份完整的 SQL Schema,例如:
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
total DECIMAL(10,2) NOT NULL,
INDEX idx_orders_user (user_id)
);
请求中应包含的内容:
- 实体(用户、产品、订单)
- 关键关系(用户拥有订单,订单包含商品)
- 规模提示(高流量、数百万条记录)
- 数据库偏好(SQL/NoSQL)—— 未指定时默认使用 SQL
触发词
| 触发词 | 示例 |
|---|
design schema | "design a schema for user authentication" |
database design | "database design for multi-tenant SaaS" |
create tables | "create tables for a blog system" |
schema for | "schema for inventory management" |
model data | "model data for real-time analytics" |
I need a database | "I need a database for tracking orders" |
design NoSQL | "design NoSQL schema for product catalog" |
关键术语
| 术语 | 定义 |
|---|
| 规范化(Normalization) | 组织数据以减少冗余(1NF → 2NF → 3NF) |
| 3NF | 第三范式 —— 列之间不存在传递依赖 |
| OLTP | 在线事务处理 —— 写密集,需要规范化 |
| OLAP | 在线分析处理 —— 读密集,受益于反规范化 |
| 外键(FK) | 引用另一张表主键的列 |
| 索引 | 加速查询的数据结构(代价是写入变慢) |
| 访问模式 | 应用读写数据的方式(查询、连接、过滤) |
| 反规范化 | 有意复制数据以加速读取 |
快速参考
| 任务 | 方法 | 关键考虑 |
|---|
| 新 Schema | 先规范化至 3NF | 领域建模优先于 UI |
| SQL vs NoSQL | 由访问模式决定 | 读写比例很重要 |
| 主键 | INT 或 UUID | 分布式系统使用 UUID |
| 外键 | 始终添加约束 | ON DELETE 策略至关重要 |
| 索引 | 外键 + WHERE 列 | 列顺序很重要 |
| 迁移 | 始终可逆 | 优先向后兼容 |
流程概览
Your Data Requirements
|
v
+-----------------------------------------------------+
| Phase 1: ANALYSIS |
| * Identify entities and relationships |
| * Determine access patterns (read vs write heavy) |
| * Choose SQL or NoSQL based on requirements |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| Phase 2: DESIGN |
| * Normalize to 3NF (SQL) or embed/reference (NoSQL) |
| * Define primary keys and foreign keys |
| * Choose appropriate data types |
| * Add constraints (UNIQUE, CHECK, NOT NULL) |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| Phase 3: OPTIMIZE |
| * Plan indexing strategy |
| * Consider denormalization for read-heavy queries |
| * Add timestamps (created_at, updated_at) |
+-----------------------------------------------------+
|
v
+-----------------------------------------------------+
| Phase 4: MIGRATE |
| * Generate migration scripts (up + down) |
| * Ensure backward compatibility |
| * Plan zero-downtime deployment |
+-----------------------------------------------------+
|
v
Production-Ready Schema
命令
| 命令 | 使用时机 | 操作 |
|---|
design schema for {domain} | 从零开始 | 完整的 Schema 生成 |
normalize {table} | 修复现有表 | 应用规范化规则 |
add indexes for {table} | 性能问题 | 生成索引策略 |
migration for {change} | Schema 演进 | 创建可逆迁移 |
review schema | 代码审查 | 审查现有 Schema |
工作流: 从 design schema 开始 → 用 normalize 迭代 → 用 add indexes 优化 → 用 migration 演进
核心原则
| 原则 | 原因 | 实现方式 |
|---|
| 对领域建模 | UI 会变,领域不会 | 实体名称反映业务概念 |
| 数据完整性优先 | 数据损坏的修复成本高昂 | 在数据库层面添加约束 |
| 针对访问模式优化 | 无法同时为两者优化 | OLTP:规范化,OLAP:反规范化 |
| 为规模做规划 | 事后改造非常痛苦 | 索引策略 + 分区方案 |
反模式
| 避免 | 原因 | 替代方案 |
|---|
| 到处使用 VARCHAR(255) | 浪费存储空间,掩盖意图 | 按字段合理设定长度 |
| 用 FLOAT 存储金额 | 舍入误差 | DECIMAL(10,2) |
| 缺少外键约束 | 产生孤立数据 | 始终定义外键 |
| 外键上没有索引 | JOIN 变慢 | 为每个外键建立索引 |
| 将日期存储为字符串 | 无法比较/排序 | DATE、TIMESTAMP 类型 |
| 查询中使用 SELECT * | 获取不必要的数据 | 明确列出所需列 |
| 不可逆的迁移 | 无法回滚 | 始终编写 DOWN 迁移 |
| 添加 NOT NULL 但不设默认值 | 破坏现有行 | 先添加可空列,回填数据,再加约束 |
验证清单
设计完 schema 后:
深入探讨:范式化(SQL)
范式
| 范式 | 规则 | 违规示例 |
|---|
| 1NF | 原子值,无重复组 | product_ids = '1,2,3' |
| 2NF | 1NF + 无部分依赖 | order_items 中的 customer_name |
| 3NF | 2NF + 无传递依赖 | 由 postal_code 推导出的 country |
第一范式(1NF)
-- BAD: Multiple values in column
CREATE TABLE orders (
id INT PRIMARY KEY,
product_ids VARCHAR(255) -- '101,102,103'
);
-- GOOD: Separate table for items
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT
);
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT REFERENCES orders(id),
product_id INT
);
第二范式(2NF)
-- BAD: customer_name depends only on customer_id
CREATE TABLE order_items (
order_id INT,
product_id INT,
customer_name VARCHAR(100), -- Partial dependency!
PRIMARY KEY (order_id, product_id)
);
-- GOOD: Customer data in separate table
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(100)
);
第三范式(3NF)
-- BAD: country depends on postal_code
CREATE TABLE customers (
id INT PRIMARY KEY,
postal_code VARCHAR(10),
country VARCHAR(50) -- Transitive dependency!
);
-- GOOD: Separate postal_codes table
CREATE TABLE postal_codes (
code VARCHAR(10) PRIMARY KEY,
country VARCHAR(50)
);
何时反范式化
| 场景 | 反范式化策略 |
|---|
| 读密集的报表 | 预计算的聚合值 |
| 昂贵的 JOIN | 缓存的派生列 |
| 分析仪表盘 | 物化视图 |
-- Denormalized for performance
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
total_amount DECIMAL(10,2), -- Calculated
item_count INT -- Calculated
);
深入探讨:数据类型
字符串类型
| 类型 | 使用场景 | 示例 |
|---|
| CHAR(n) | 固定长度 | 州代码、ISO 日期 |
| VARCHAR(n) | 可变长度 | 姓名、邮箱 |
| TEXT | 长内容 | 文章、描述 |
-- Good sizing
email VARCHAR(255)
phone VARCHAR(20)
country_code CHAR(2)
数值类型
| 类型 | 范围 | 使用场景 |
|---|
| TINYINT | -128 到 127 | 年龄、状态码 |
| SMALLINT | -32K 到 32K | 数量 |
| INT | -2.1B 到 2.1B | ID、计数 |
| BIGINT | 非常大 | 大 ID、时间戳 |
| DECIMAL(p,s) | 精确精度 | 金额 |
| FLOAT/DOUBLE | 近似 | 科学数据 |
-- ALWAYS use DECIMAL for money
price DECIMAL(10, 2) -- $99,999,999.99
-- NEVER use FLOAT for money
price FLOAT -- Rounding errors!
日期/时间类型
DATE -- 2025-10-31
TIME -- 14:30:00
DATETIME -- 2025-10-31 14:30:00
TIMESTAMP -- Auto timezone conversion
-- Always store in UTC
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
布尔值
-- PostgreSQL
is_active BOOLEAN DEFAULT TRUE
-- MySQL
is_active TINYINT(1) DEFAULT 1
深入探讨:索引策略
何时创建索引
| 始终建索引 | 原因 |
|---|
| 外键 | 加速 JOIN |
| WHERE 子句列 | 加速过滤 |
| ORDER BY 列 | 加速排序 |
| 唯一约束 | 强制唯一性 |
-- Foreign key index
CREATE INDEX idx_orders_customer ON orders(customer_id);
-- Query pattern index
CREATE INDEX idx_orders_status_date ON orders(status, created_at);
索引类型
| 类型 | 最适合 | 示例 |
|---|
| B-Tree | 范围、等值 | price > 100 |
| Hash | 仅精确匹配 | email = '[email protected]' |
| 全文 | 文本搜索 | MATCH AGAINST |
| 部分 | 行的子集 | WHERE is_active = true |
复合索引顺序
CREATE INDEX idx_customer_status ON orders(customer_id, status);
-- Uses index (customer_id first)
SELECT * FROM orders WHERE customer_id = 123;
SELECT * FROM orders WHERE customer_id = 123 AND status = 'pending';
-- Does NOT use index (status alone)
SELECT * FROM orders WHERE status = 'pending';
规则: 选择性最高的列在前,或单独查询最多的列在前。
索引陷阱
| 陷阱 | 问题 | 解决方案 |
|---|
| 过度建索引 | 写入变慢 | 只为被查询的列建索引 |
| 列顺序错误 | 索引未被使用 | 匹配查询模式 |
| 缺少 FK 索引 | JOIN 变慢 | 始终为 FK 建索引 |
深入探讨:约束
主键
-- Auto-increment (simple)
id INT AUTO_INCREMENT PRIMARY KEY
-- UUID (distributed systems)
id CHAR(36) PRIMARY KEY DEFAULT (UUID())
-- Composite (junction tables)
PRIMARY KEY (student_id, course_id)
外键
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE CASCADE -- Delete children with parent
ON DELETE RESTRICT -- Prevent deletion if referenced
ON DELETE SET NULL -- Set to NULL when parent deleted
ON UPDATE CASCADE -- Update children when parent changes
| 策略 | 何时使用 |
|---|
| CASCADE | 有依赖的数据(order_items) |
| RESTRICT | 重要引用(防止误操作) |
| SET NULL | 可选关系 |
其他约束
-- Unique
email VARCHAR(255) UNIQUE NOT NULL
-- Composite unique
UNIQUE (student_id, course_id)
-- Check
price DECIMAL(10,2) CHECK (price >= 0)
discount INT CHECK (discount BETWEEN 0 AND 100)
-- Not null
name VARCHAR(100) NOT NULL
深入探讨:关系模式
一对多
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT NOT NULL REFERENCES customers(id)
);
CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
product_id INT NOT NULL,
quantity INT NOT NULL
);
多对多
-- Junction table
CREATE TABLE enrollments (
student_id INT REFERENCES students(id) ON DELETE CASCADE,
course_id INT REFERENCES courses(id) ON DELETE CASCADE,
enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (student_id, course_id)
);
自引用
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
manager_id INT REFERENCES employees(id)
);
多态
-- Approach 1: Separate FKs (stronger integrity)
CREATE TABLE comments (
id INT PRIMARY KEY,
content TEXT NOT NULL,
post_id INT REFERENCES posts(id),
photo_id INT REFERENCES photos(id),
CHECK (
(post_id IS NOT NULL AND photo_id IS NULL) OR
(post_id IS NULL AND photo_id IS NOT NULL)
)
);
-- Approach 2: Type + ID (flexible, weaker integrity)
CREATE TABLE comments (
id INT PRIMARY KEY,
content TEXT NOT NULL,
commentable_type VARCHAR(50) NOT NULL,
commentable_id INT NOT NULL
);
深入探讨:NoSQL 设计(MongoDB)
嵌入 vs 引用
| 因素 | 嵌入 | 引用 |
|---|
| 访问模式 | 一起读取 | 分别读取 |
| 关系 | 1:少 | 1:多 |
| 文档大小 | 小 | 接近 16MB |
| 更新频率 | 很少 | 频繁 |
嵌入式文档
{
"_id": "order_123",
"customer": {
"id": "cust_456",
"name": "Jane Smith",
"email": "[email protected]"
},
"items": [
{ "product_id": "prod_789", "quantity": 2, "price": 29.99 }
],
"total": 109.97
}
引用文档
{
"_id": "order_123",
"customer_id": "cust_456",
"item_ids": ["item_1", "item_2"],
"total": 109.97
}
MongoDB 索引
// Single field
db.users.createIndex({ email: 1 }, { unique: true });
// Composite
db.orders.createIndex({ customer_id: 1, created_at: -1 });
// Text search
db.articles.createIndex({ title: "text", content: "text" });
// Geospatial
db.stores.createIndex({ location: "2dsphere" });
深入探讨:迁移
迁移最佳实践
| 实践 | 原因 |
|---|
| 始终可逆 | 需要回滚 |
| 向后兼容 | 零停机部署 |
| 先 Schema 后数据 | 关注点分离 |
| 在预发布环境测试 | 及早发现问题 |
添加列(零停机)
-- Step 1: Add nullable column
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Step 2: Deploy code that writes to new column
-- Step 3: Backfill existing rows
UPDATE users SET phone = '' WHERE phone IS NULL;
-- Step 4: Make required (if needed)
ALTER TABLE users MODIFY phone VARCHAR(20) NOT NULL;
重命名列(零停机)
-- Step 1: Add new column
ALTER TABLE users ADD COLUMN email_address VARCHAR(255);
-- Step 2: Copy data
UPDATE users SET email_address = email;
-- Step 3: Deploy code reading from new column
-- Step 4: Deploy code writing to new column
-- Step 5: Drop old column
ALTER TABLE users DROP COLUMN email;
迁移模板
-- Migration: YYYYMMDDHHMMSS_description.sql
-- UP
BEGIN;
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
CREATE INDEX idx_users_phone ON users(phone);
COMMIT;
-- DOWN
BEGIN;
DROP INDEX idx_users_phone ON users;
ALTER TABLE users DROP COLUMN phone;
COMMIT;
深入探讨:性能优化
查询分析
EXPLAIN SELECT * FROM orders
WHERE customer_id = 123 AND status = 'pending';
| 关注项 | 含义 |
|---|
| type: ALL | 全表扫描(差) |
| type: ref | 使用了索引(好) |
| key: NULL | 未使用索引 |
| rows: 很高 | 扫描了大量行 |
N+1 查询问题
# BAD: N+1 queries
orders = db.query("SELECT * FROM orders")
for order in orders:
customer = db.query(f"SELECT * FROM customers WHERE id = {order.customer_id}")
# GOOD: Single JOIN
results = db.query("""
SELECT orders.*, customers.name
FROM orders
JOIN customers ON orders.customer_id = customers.id
""")
优化技巧
| 技巧 | 使用时机 |
|---|
| 添加索引 | WHERE/ORDER BY 缓慢 |
| 反规范化 | JOIN 开销大 |
| 分页 | 结果集很大 |
| 缓存 | 重复查询 |
| 只读副本 | 读负载高 |
| 分区 | 表非常大 |
扩展点
- 数据库特定模式: 添加 MySQL vs PostgreSQL vs SQLite 的差异
- 高级模式: 时间序列、事件溯源、CQRS、多租户
- ORM 集成: TypeORM、Prisma、SQLAlchemy 模式
- 监控: 查询性能跟踪、慢查询告警