279 lines
5.5 KiB
Plaintext
279 lines
5.5 KiB
Plaintext
---
|
||
description: Prisma 数据库设计规范和最佳实践
|
||
globs:
|
||
- "backend/prisma/**/*.prisma"
|
||
alwaysApply: false
|
||
---
|
||
|
||
# 数据库设计规范
|
||
|
||
## Prisma Schema 规范
|
||
|
||
### 表结构要求
|
||
|
||
所有业务表必须包含以下字段:
|
||
|
||
```prisma
|
||
model YourModel {
|
||
id Int @id @default(autoincrement())
|
||
tenantId Int @map("tenant_id") /// 租户ID(必填)
|
||
|
||
// 业务字段...
|
||
name String
|
||
description String?
|
||
|
||
// 审计字段
|
||
validState Int @default(1) @map("valid_state") /// 1-有效,2-失效
|
||
creator Int? /// 创建人ID
|
||
modifier Int? /// 修改人ID
|
||
createTime DateTime @default(now()) @map("create_time")
|
||
modifyTime DateTime @updatedAt @map("modify_time")
|
||
|
||
// 关系
|
||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||
creatorUser User? @relation("YourModelCreator", fields: [creator], references: [id], onDelete: SetNull)
|
||
modifierUser User? @relation("YourModelModifier", fields: [modifier], references: [id], onDelete: SetNull)
|
||
|
||
@@map("your_table_name")
|
||
}
|
||
```
|
||
|
||
### 字段命名规范
|
||
|
||
- Prisma 模型使用 camelCase:`tenantId`, `createTime`
|
||
- 数据库列使用 snake_case:`tenant_id`, `create_time`
|
||
- 使用 `@map()` 映射字段名
|
||
- 使用 `@@map()` 映射表名
|
||
|
||
### 状态字段
|
||
|
||
使用 `validState` 表示数据有效性:
|
||
|
||
- `1` - 有效
|
||
- `2` - 失效(软删除)
|
||
|
||
```prisma
|
||
validState Int @default(1) @map("valid_state")
|
||
```
|
||
|
||
## 关系设计
|
||
|
||
### 一对多关系
|
||
|
||
```prisma
|
||
model Tenant {
|
||
id Int @id @default(autoincrement())
|
||
users User[]
|
||
}
|
||
|
||
model User {
|
||
id Int @id @default(autoincrement())
|
||
tenantId Int @map("tenant_id")
|
||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||
}
|
||
```
|
||
|
||
### 多对多关系
|
||
|
||
使用显式中间表:
|
||
|
||
```prisma
|
||
model User {
|
||
id Int @id @default(autoincrement())
|
||
roles UserRole[]
|
||
}
|
||
|
||
model Role {
|
||
id Int @id @default(autoincrement())
|
||
users UserRole[]
|
||
}
|
||
|
||
model UserRole {
|
||
userId Int @map("user_id")
|
||
roleId Int @map("role_id")
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
||
|
||
@@id([userId, roleId])
|
||
@@map("user_roles")
|
||
}
|
||
```
|
||
|
||
### 一对一关系
|
||
|
||
```prisma
|
||
model User {
|
||
id Int @id @default(autoincrement())
|
||
teacher Teacher?
|
||
}
|
||
|
||
model Teacher {
|
||
id Int @id @default(autoincrement())
|
||
userId Int @unique @map("user_id")
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
}
|
||
```
|
||
|
||
### 级联删除规则
|
||
|
||
- 强依赖关系:`onDelete: Cascade`
|
||
- 弱依赖关系:`onDelete: SetNull`(字段必须可选)
|
||
- 保护性关系:`onDelete: Restrict`
|
||
|
||
## 索引设计
|
||
|
||
### 自动索引
|
||
|
||
- 主键自动创建索引
|
||
- 外键字段自动创建索引
|
||
- `@unique` 字段自动创建唯一索引
|
||
|
||
### 复合索引
|
||
|
||
```prisma
|
||
model User {
|
||
tenantId Int
|
||
username String
|
||
|
||
@@unique([tenantId, username])
|
||
@@index([tenantId, validState])
|
||
}
|
||
```
|
||
|
||
### 性能优化索引
|
||
|
||
为频繁查询的字段添加索引:
|
||
|
||
```prisma
|
||
model Contest {
|
||
tenantId Int
|
||
status Int
|
||
startTime DateTime
|
||
|
||
@@index([tenantId, status])
|
||
@@index([tenantId, startTime])
|
||
}
|
||
```
|
||
|
||
## Prisma 查询最佳实践
|
||
|
||
### 使用 include 预加载关联
|
||
|
||
避免 N+1 查询问题:
|
||
|
||
```typescript
|
||
// ✅ 好的做法 - 使用 include 预加载
|
||
const users = await prisma.user.findMany({
|
||
where: { tenantId },
|
||
include: {
|
||
roles: {
|
||
include: {
|
||
role: true,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
// ❌ 不好的做法 - N+1 查询
|
||
const users = await prisma.user.findMany({
|
||
where: { tenantId },
|
||
});
|
||
for (const user of users) {
|
||
user.roles = await prisma.userRole.findMany({
|
||
where: { userId: user.id },
|
||
});
|
||
}
|
||
```
|
||
|
||
### 使用 select 精简字段
|
||
|
||
只查询需要的字段:
|
||
|
||
```typescript
|
||
const users = await prisma.user.findMany({
|
||
where: { tenantId },
|
||
select: {
|
||
id: true,
|
||
username: true,
|
||
nickname: true,
|
||
// 不查询 password 等敏感字段
|
||
},
|
||
});
|
||
```
|
||
|
||
### 分页查询
|
||
|
||
```typescript
|
||
const users = await prisma.user.findMany({
|
||
where: { tenantId, validState: 1 },
|
||
skip: (page - 1) * pageSize,
|
||
take: pageSize,
|
||
orderBy: { createTime: "desc" },
|
||
});
|
||
|
||
const total = await prisma.user.count({
|
||
where: { tenantId, validState: 1 },
|
||
});
|
||
```
|
||
|
||
### 事务处理
|
||
|
||
使用 `$transaction` 确保数据一致性:
|
||
|
||
```typescript
|
||
await prisma.$transaction(async (tx) => {
|
||
// 创建用户
|
||
const user = await tx.user.create({
|
||
data: {
|
||
username: "test",
|
||
tenantId,
|
||
},
|
||
});
|
||
|
||
// 创建用户角色关联
|
||
await tx.userRole.createMany({
|
||
data: roleIds.map((roleId) => ({
|
||
userId: user.id,
|
||
roleId,
|
||
})),
|
||
});
|
||
});
|
||
```
|
||
|
||
## 数据迁移
|
||
|
||
### 创建迁移
|
||
|
||
```bash
|
||
# 开发环境 - 创建并应用迁移
|
||
pnpm prisma:migrate:dev
|
||
|
||
# 生产环境 - 只应用迁移
|
||
pnpm prisma:migrate:deploy
|
||
```
|
||
|
||
### 迁移命名
|
||
|
||
使用描述性的迁移名称:
|
||
|
||
```bash
|
||
prisma migrate dev --name add_contest_module
|
||
prisma migrate dev --name add_user_avatar_field
|
||
```
|
||
|
||
### 迁移注意事项
|
||
|
||
- 迁移前备份数据库
|
||
- 测试迁移在开发环境的执行
|
||
- 生产环境使用 `migrate deploy` 而不是 `migrate dev`
|
||
- 不要手动修改已应用的迁移文件
|
||
|
||
## 性能优化清单
|
||
|
||
- [ ] 频繁查询的字段添加了索引
|
||
- [ ] 使用 `include` 预加载关联数据
|
||
- [ ] 使用 `select` 只查询需要的字段
|
||
- [ ] 实现了分页查询
|
||
- [ ] 复杂操作使用了事务
|
||
- [ ] 避免了 N+1 查询问题
|