404 数据库设计与优化
AI 辅助数据库 Schema 设计、索引优化、查询调优与备份策略
学习目标
完成本课程后,你将能够:
一、Schema 设计原则
用 Codex 审查 Schema 设计
/review 请审查我的 Prisma Schema,检查以下问题:
1. 是否存在 N+1 查询风险
2. 索引设计是否合理
3. 字段类型选择是否正确
4. 是否存在数据冗余
5. JSON 字段是否应该拆表常见 Schema 问题与修复
| 问题 | 示例 | 修复方案 |
|---|---|---|
| 缺失索引 | 按 status 查询无索引 | 添加 `@@index([status])` |
| JSON 滥用 | 标签存为 JSON 数组 | 拆为独立 Tag 表 + 关联表 |
| 文本字段过大 | 所有 text 用 TEXT 类型 | 短字符串用 VARCHAR(n) |
| 缺失级联删除 | 删项目后任务残留 | 添加 `onDelete: Cascade` |
| 时序数据未分区 | 日志表无限增长 | 按月份分区表 |
索引设计指南
// ✅ 好的索引设计
model Task {
id String @id @default(cuid())
title String
status TaskStatus
priority Priority
dueDate DateTime?
projectId String
assigneeId String?
createdAt DateTime @default(now())
// 复合索引:覆盖最常见的查询组合
@@index([projectId, status]) // 按项目 + 状态筛选
@@index([assigneeId, status]) // 我的任务筛选
@@index([status, priority]) // 优先级排序
@@index([dueDate]) // 截止日期排序(B-tree 索引)
@@index([createdAt]) // 时间范围查询
}二、迁移脚本管理
生成迁移
Prisma 迁移命令速查:
npx prisma migrate dev --name add_tags_table
→ 开发环境:生成并应用迁移
npx prisma migrate dev --create-only
→ 只生成迁移文件,手动审核后执行
npx prisma migrate deploy
→ 生产环境:执行所有待处理迁移
npx prisma migrate status
→ 查看迁移状态迁移脚本审核清单
Codex 生成的迁移脚本上线前必须检查:
数据迁移示例
请为 Task 表生成数据迁移脚本:
- 将现有的 status 字段从 VARCHAR 改为 ENUM 类型
- 迁移期间保持服务可用
- 包含回滚脚本
步骤:
1. 新增临时 ENUM 列
2. 数据批量迁移(分批 1000 条)
3. 切换读写列
4. 删除旧列三、查询优化
用 Codex 分析慢查询
/debug 以下 API 响应时间超过 2 秒,请分析原因并给出优化方案:
GET /api/tasks?projectId=xxx&page=1&limit=20
Prisma 查询:
const tasks = await prisma.task.findMany({
where: { projectId },
include: {
assignee: true,
tags: { include: { tag: true } },
comments: { include: { author: true } },
},
orderBy: { createdAt: 'desc' },
take: 20,
skip: 0,
});典型优化方案
**问题 1:N+1 查询**
使用 include 已经避免了 N+1,但嵌套 include 仍可能产生多余的 JOIN。
优化:对非必要字段使用 select 替代 include**问题 2:返回数据过多**
// ❌ 返回所有字段(包括大文本)
include: { assignee: true }
// ✅ 只选择需要的字段
const tasks = await prisma.task.findMany({
where: { projectId },
select: {
id: true,
title: true,
status: true,
priority: true,
dueDate: true,
assignee: {
select: { id: true, name: true, avatar: true },
},
_count: {
select: { comments: true },
},
},
orderBy: { createdAt: 'desc' },
take: 20,
skip: 0,
});**问题 3:缺失分页游标**
// 基于游标的分页(比 offset 更高效)
const tasks = await prisma.task.findMany({
where: { projectId },
take: 20,
cursor: cursor ? { id: cursor } : undefined,
skip: cursor ? 1 : 0, // 跳过游标本身
orderBy: { createdAt: 'desc' },
});查询优化效果对比
| 优化项 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 添加索引 | 800ms | 45ms | 94% |
| select 替代 include | 350ms | 80ms | 77% |
| 游标分页 | 200ms | 30ms | 85% |
| Redis 缓存热数据 | 120ms | 5ms | 96% |
四、缓存策略
多级缓存架构
请求 → Redis 缓存(热数据)
↓ 未命中
API 服务(应用层缓存)
↓ 未命中
PostgreSQL(数据库层)Redis 缓存实现
// backend/src/services/cache.ts
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
export class CacheService {
static async getOrSet<T>(
key: string,
fetchFn: () => Promise<T>,
ttl = 300 // 5分钟默认
): Promise<T> {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const data = await fetchFn();
await redis.setex(key, ttl, JSON.stringify(data));
return data;
}
static async invalidate(pattern: string): Promise<void> {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
}
}
// 使用示例
const tasks = await CacheService.getOrSet(
`tasks:board:${userId}`,
() => prisma.task.findMany({ where: { assigneeId: userId } }),
60 // 1分钟
);
// 创建/更新任务后失效缓存
await CacheService.invalidate('tasks:*');五、备份与恢复策略
用 Codex 生成备份方案
请为 PostgreSQL 生成自动化备份方案:
- 每日全量备份(凌晨 3 点)
- 每 6 小时增量备份(WAL 归档)
- 保留最近 7 天全量 + 30 天增量
- 备份文件加密存储到 S3
- 包含恢复验证脚本备份脚本
#!/bin/bash
# backup.sh — PostgreSQL 自动备份
DB_NAME="taskflow"
BACKUP_DIR="/backups/postgres"
S3_BUCKET="s3://taskflow-backups"
RETENTION_DAYS=7
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.dump"
# 执行备份
pg_dump -Fc -d "$DB_NAME" -f "$BACKUP_FILE"
# 加密
gpg --encrypt --recipient backup@company.com "$BACKUP_FILE"
# 上传到 S3
aws s3 cp "${BACKUP_FILE}.gpg" "${S3_BUCKET}/daily/"
# 清理过期备份
find "$BACKUP_DIR" -name "*.dump*" -mtime +$RETENTION_DAYS -delete
echo "Backup completed: ${BACKUP_FILE}.gpg"恢复验证
备份策略检查清单:
- [ ] 最近一次全量备份是否成功
- [ ] WAL 归档是否持续写入
- [ ] 恢复流程是否通过演练
- [ ] RPO(恢复点目标)是否达标
- [ ] RTO(恢复时间目标)是否达标六、本课总结
**核心要点:**
- Schema 设计后让 Codex 审查,提前发现 N+1、索引缺失等问题
- 迁移脚本上线前必须人工审核,特别是 DROP 和 ALTER 操作
- 查询优化三板斧:添加索引、select 替代 include、游标分页
- Redis 缓存热数据,配合 Cache-Aside 模式管理失效
- 备份不仅要执行,还要定期验证恢复流程
**下一步:** 模块 405 — 部署与运维