数据库设计Skill database-design

数据库设计技能专注于创建高效、可扩展的数据库系统,包括模式规范化、关系设计、索引策略、查询优化、迁移规划和SQL/NoSQL选择。适用于软件开发和数据工程项目,提升应用性能、数据完整性和可维护性。关键词:数据库设计、模式、索引、查询优化、SQL、NoSQL、迁移、规范化、关系型数据库、NoSQL数据库、数据建模。

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

名称: 数据库设计 描述: 设计可扩展、规范化的数据库模式,包括关系、索引、约束和迁移策略,适用于关系型和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):

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

查询优化

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)
□ 所有表有主键
□ 外国键带有适当CASCADE/SET NULL
□ 检查约束用于数据验证
□ 适当NOT NULL约束
□ 唯一约束用于业务规则

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

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

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

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

资源


记住: 好的数据库设计是可扩展应用的基础。前期投入时间进行适当的模式设计和索引。