数据库设计Skill database-design

数据库设计技能用于创建和优化数据库结构,确保数据完整性、性能和可扩展性。它包括模式设计、索引策略、关系建模、SQL与NoSQL选择、查询优化和迁移计划。关键词:数据库设计,schema建模,索引优化,SQL数据库,NoSQL数据库,查询性能,数据迁移,可扩展性。

架构设计 0 次安装 0 次浏览 更新于 3/18/2026

name: 数据库设计 description: 设计可扩展、规范化的数据库模式,包括正确的关系、索引、约束和迁移策略,适用于关系型和NoSQL数据库。使用场景:设计数据库模式、规划表关系和外来键、创建索引以优化查询、定义约束和验证、设计可扩展的数据模型、规划数据库迁移、选择SQL和NoSQL之间、实施分片策略、优化查询性能或建立数据完整性规则。

数据库设计 - 模式、索引和查询优化

何时使用此技能

  • 设计新的数据库模式和模型
  • 规划表关系和外来键约束
  • 创建索引以优化查询性能
  • 定义数据库约束和验证规则
  • 设计可扩展性和未来增长
  • 规划数据库迁移策略
  • 选择SQL和NoSQL数据库之间
  • 实施数据库分片或分区
  • 规范化数据以减少冗余
  • 优化慢速数据库查询
  • 设计审计跟踪和软删除
  • 规划备份和灾难恢复策略

何时使用此技能

  • 设计数据库模式、选择SQL/NoSQL、优化查询、规划迁移或解决数据建模问题。
  • 当处理相关任务或功能时
  • 在需要此专业知识的开发过程中

使用场景:设计数据库模式、选择SQL/NoSQL、优化查询、规划迁移或解决数据建模问题。

核心原则

  1. 先规范化,需要时再反规范化 - 从正确规范化开始
  2. 战略性地索引 - 平衡查询速度与写入开销
  3. 为扩展而设计 - 从一开始就考虑增长模式
  4. 选择正确的工具 - SQL与NoSQL取决于使用案例
  5. 迁移是永远的 - 仔细规划模式变更

模式设计基础

1. 规范化(减少冗余)

-- ❌ 反规范化 - 数据重复
CREATE TABLE orders (
  id INT PRIMARY KEY,
  customer_name VARCHAR(100),
  customer_email VARCHAR(100),
  customer_address TEXT,  -- 每个订单都重复!
  product_name VARCHAR(200),
  product_price DECIMAL(10,2),  -- 重复的产品数据
  quantity INT
);

-- ✅ 规范化 - 分离关注点
CREATE TABLE customers (
  id INT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(100) UNIQUE NOT NULL,
  address TEXT
);

CREATE TABLE products (
  id INT PRIMARY KEY,
  name VARCHAR(200) NOT NULL,
  price DECIMAL(10,2) NOT NULL,
  stock INT DEFAULT 0
);

CREATE TABLE orders (
  id INT PRIMARY KEY,
  customer_id INT NOT NULL REFERENCES customers(id),
  created_at TIMESTAMP DEFAULT NOW(),
  status VARCHAR(20) DEFAULT 'pending'
);

CREATE TABLE order_items (
  id INT PRIMARY KEY,
  order_id INT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
  product_id INT NOT NULL REFERENCES products(id),
  quantity INT NOT NULL CHECK (quantity > 0),
  price_at_purchase DECIMAL(10,2) NOT NULL  -- 历史快照
);

2. 主键和外键

-- ✅ 自增整数(传统)
CREATE TABLE users (
  id SERIAL PRIMARY KEY,  -- PostgreSQL
  -- id INT AUTO_INCREMENT PRIMARY KEY, -- MySQL
  email VARCHAR(255) UNIQUE NOT NULL
);

-- ✅ UUID(分布式系统,无冲突)
CREATE TABLE posts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id),
  title VARCHAR(200) NOT NULL
);

-- ✅ 复合主键(连接表)
CREATE TABLE user_roles (
  user_id INT NOT NULL REFERENCES users(id),
  role_id INT NOT NULL REFERENCES roles(id),
  granted_at TIMESTAMP DEFAULT NOW(),
  PRIMARY KEY (user_id, role_id)
);

-- ✅ 外键与级联选项
CREATE TABLE comments (
  id INT PRIMARY KEY,
  post_id INT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,  -- 删除帖子时删除评论
  user_id INT NOT NULL REFERENCES users(id) ON DELETE SET NULL,  -- 保留评论,将用户设为空
  content TEXT NOT NULL
);

3. 索引优化性能

-- ✅ 单列索引(频繁查询)
CREATE INDEX idx_users_email ON users(email);

-- ✅ 复合索引(多列查询)
CREATE INDEX idx_orders_customer_date 
ON orders(customer_id, created_at DESC);

-- ✅ 部分索引(条件性)
CREATE INDEX idx_active_users 
ON users(email) WHERE status = 'active';

-- ✅ 全文搜索索引
CREATE INDEX idx_posts_search 
ON posts USING GIN(to_tsvector('english', title || ' ' || content));

-- ✅ 唯一索引(强制执行约束 + 加速查找)
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);

-- ❌ 过度索引(减慢写入)
-- 不要盲目索引每一列
-- 索引占用磁盘空间并减慢 INSERT/UPDATE/DELETE

索引策略:

索引时机:
✓ 频繁在 WHERE 子句中使用
✓ 在 JOIN 条件中使用
✓ 在 ORDER BY / GROUP BY 中使用
✓ 高基数(许多唯一值)

不索引时机:
✗ 小表(< 1000 行)
✗ 低基数(如布尔、状态只有2-3个值)
✗ 频繁更新的列(除非读取 >> 写入)
✗ 已由复合索引覆盖

4. 关系

-- ✅ 一对多:用户有多个帖子
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100));
CREATE TABLE posts (
  id INT PRIMARY KEY,
  user_id INT NOT NULL REFERENCES users(id),
  title VARCHAR(200)
);

-- ✅ 多对多:用户 ↔ 角色(连接表)
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100));
CREATE TABLE roles (id INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE user_roles (
  user_id INT REFERENCES users(id),
  role_id INT REFERENCES roles(id),
  PRIMARY KEY (user_id, role_id)
);

-- ✅ 一对一:用户有一个个人资料
CREATE TABLE users (id INT PRIMARY KEY, email VARCHAR(255));
CREATE TABLE profiles (
  id INT PRIMARY KEY,
  user_id INT UNIQUE NOT NULL REFERENCES users(id),  -- UNIQUE 强制执行1:1
  bio TEXT,
  avatar_url VARCHAR(500)
);

-- ✅ 自引用:树形结构
CREATE TABLE categories (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  parent_id INT REFERENCES categories(id)  -- 指向自身
);

5. 数据类型

-- ✅ 选择合适类型
CREATE TABLE products (
  id INT PRIMARY KEY,
  name VARCHAR(200) NOT NULL,           -- 变长字符串
  description TEXT,                     -- 长文本
  price DECIMAL(10,2) NOT NULL,         -- 精确货币精度
  stock INT DEFAULT 0,                  -- 整数
  weight FLOAT,                         -- 近似数
  is_active BOOLEAN DEFAULT true,       -- 真/假
  created_at TIMESTAMP DEFAULT NOW(),   -- 日期 + 时间
  metadata JSONB,                       -- JSON 数据(PostgreSQL)
  tags TEXT[]                           -- 数组(PostgreSQL)
);

-- ❌ 错误类型
-- price FLOAT - ❌ 浮点误差对货币不利
-- created_at VARCHAR(50) - ❌ 使用适当日期时间类型
-- status INT - ❌ 使用 ENUM 或 VARCHAR 提高可读性

SQL 与 NoSQL 选择

何时使用 SQL(PostgreSQL, MySQL)

✓ 复杂关系和连接
✓ 需要 ACID 事务
✓ 结构化、可预测数据
✓ 复杂查询和聚合
✓ 需要强一致性
✓ 示例:金融系统、电子商务、CRM

何时使用 NoSQL

文档存储(MongoDB, Firestore):

✓ 灵活、演进模式
✓ 嵌套/分层数据
✓ 快速原型设计
✓ 文档中心访问模式
✓ 示例:CMS、目录、用户资料

键值存储(Redis, DynamoDB):

✓ 简单键查找
✓ 缓存层
✓ 会话存储
✓ 速率限制计数器
✓ 示例:缓存、会话、实时排行榜

时间序列(InfluxDB, TimescaleDB):

✓ 时间戳数据
✓ 高写入吞吐量
✓ 时间聚合
✓ 示例:指标、日志、IoT 传感器

查询优化

1. 识别慢查询

-- PostgreSQL - 解释查询计划
EXPLAIN ANALYZE
SELECT u.name, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
GROUP BY u.id, u.name;

/*
输出显示:
- Seq Scan(差) vs Index Scan(好)
- 执行时间
- 扫描行数 vs 返回行数
*/

2. 避免 N+1 查询

// ❌ N+1 问题 - 1 查询 + N 查询每个用户
const users = await db.query('SELECT * FROM users');
for (const user of users) {
  const posts = await db.query(
    'SELECT * FROM posts WHERE user_id = ?', 
    [user.id]
  ); // N 查询!
}

// ✅ 单查询带 JOIN
const results = await db.query(`
  SELECT 
    u.id, u.name, u.email,
    p.id as post_id, p.title as post_title
  FROM users u
  LEFT JOIN posts p ON p.user_id = u.id
`);

// ✅ 使用 Prisma ORM 急切加载
const users = await prisma.user.findMany({
  include: {
    posts: true  // 高效加载相关帖子
  }
});

3. 使用高效连接

-- ✅ INNER JOIN - 只匹配行
SELECT u.name, p.title
FROM users u
INNER JOIN posts p ON p.user_id = u.id;

-- ✅ LEFT JOIN - 所有用户,即使无帖子
SELECT u.name, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
GROUP BY u.id, u.name;

-- ❌ 避免 SELECT 中子查询可能时
SELECT 
  u.name,
  (SELECT COUNT(*) FROM posts WHERE user_id = u.id) as post_count  -- 慢!
FROM users u;

-- ✅ 更好:使用 JOIN
SELECT u.name, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
GROUP BY u.id, u.name;

4. 分页

-- ✅ 偏移分页(简单但深页慢)
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 20 OFFSET 40;  -- 第 3 页

-- ✅ 游标分页(深页快)
SELECT * FROM posts
WHERE created_at < '2024-01-01 12:00:00'
ORDER BY created_at DESC
LIMIT 20;

5. 聚合

-- ✅ 高效计数
SELECT COUNT(*) FROM users WHERE status = 'active';

-- ✅ 分组聚合
SELECT 
  category,
  COUNT(*) as total,
  AVG(price) as avg_price,
  MAX(price) as max_price
FROM products
GROUP BY category
HAVING COUNT(*) > 10;  -- HAVING 过滤组,WHERE 过滤行

-- ✅ 窗口函数(PostgreSQL)
SELECT 
  name,
  salary,
  AVG(salary) OVER (PARTITION BY department) as dept_avg_salary
FROM employees;

迁移策略

1. 使用 Prisma 的模式迁移

// schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
  
  @@index([authorId])
}
# 生成迁移
npx prisma migrate dev --name add_post_table

# 应用到生产
npx prisma migrate deploy

2. 安全迁移模式

-- ✅ 添加列(安全 - 不锁表)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- ✅ 并发添加索引(PostgreSQL)
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

-- ❌ 风险 - 锁整个表
ALTER TABLE users ALTER COLUMN email TYPE TEXT;

-- ✅ 更好:多步骤迁移
-- 步骤 1: 添加新列
ALTER TABLE users ADD COLUMN email_new TEXT;

-- 步骤 2: 回填数据
UPDATE users SET email_new = email;

-- 步骤 3: 交换列(单独部署)
ALTER TABLE users DROP COLUMN email;
ALTER TABLE users RENAME COLUMN email_new TO email;

3. 数据迁移

// migration-001-backfill-slugs.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

async function up() {
  const posts = await prisma.post.findMany({
    where: { slug: null }
  });
  
  for (const post of posts) {
    await prisma.post.update({
      where: { id: post.id },
      data: { slug: generateSlug(post.title) }
    });
  }
}

function generateSlug(title: string) {
  return title.toLowerCase().replace(/\s+/g, '-');
}

高级模式

1. 软删除

-- ✅ 保留删除记录
CREATE TABLE posts (
  id INT PRIMARY KEY,
  title VARCHAR(200),
  deleted_at TIMESTAMP NULL  -- NULL = 未删除
);

-- 查询仅活动记录
SELECT * FROM posts WHERE deleted_at IS NULL;

-- “删除”记录(软删除)
UPDATE posts SET deleted_at = NOW() WHERE id = 123;

-- 创建视图方便
CREATE VIEW active_posts AS
SELECT * FROM posts WHERE deleted_at IS NULL;

2. 审计跟踪

-- ✅ 跟踪所有变更
CREATE TABLE users_audit (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL,
  field_changed VARCHAR(50),
  old_value TEXT,
  new_value TEXT,
  changed_by INT REFERENCES users(id),
  changed_at TIMESTAMP DEFAULT NOW()
);

-- 触发器自动填充
CREATE OR REPLACE FUNCTION audit_user_changes()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO users_audit (user_id, field_changed, old_value, new_value)
  VALUES (OLD.id, 'email', OLD.email, NEW.email);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER user_email_audit
AFTER UPDATE OF email ON users
FOR EACH ROW
EXECUTE FUNCTION audit_user_changes();

3. 多态关联

-- ✅ 多种资源类型评论
CREATE TABLE comments (
  id INT PRIMARY KEY,
  commentable_type VARCHAR(50) NOT NULL,  -- 'Post', 'Photo', 'Video'
  commentable_id INT NOT NULL,
  content TEXT NOT NULL,
  
  -- 多态查找复合索引
  INDEX idx_commentable (commentable_type, commentable_id)
);

-- 查询帖子评论
SELECT * FROM comments 
WHERE commentable_type = 'Post' AND commentable_id = 123;

数据库检查清单

模式设计:
□ 适当规范化(至少 3NF)
□ 所有表有主键
□ 外键带适当级联/设为空
□ 检查约束数据验证
□ 适当地方有 NOT NULL 约束
□ 唯一约束业务规则

索引:
□ 外键已索引
□ WHERE 子句列已索引
□ JOIN 列已索引
□ 多列查询复合索引
□ 过滤查询部分索引
□ 无冗余索引

性能:
□ 慢查询 EXPLAIN ANALYZE
□ 消除 N+1 查询
□ 适当使用 JOIN vs 子查询
□ 实现分页
□ 配置连接池

迁移:
□ 所有变更在迁移文件中
□ 回滚能力已测试
□ 安全列添加/移除
□ 约束前数据回填
□ 生产零停机迁移

监控:
□ 启用慢查询日志
□ 跟踪连接池指标
□ 监控磁盘使用
□ 检查复制延迟(如果适用)
□ 定期 VACUUM(PostgreSQL)

资源


记住:良好数据库设计是可扩展应用的基础。前期投资时间于正确模式设计和索引。