182 lines
5.4 KiB
Markdown
182 lines
5.4 KiB
Markdown
# 数据库无外键设计说明
|
|
|
|
## 📋 设计原则
|
|
|
|
### 🚫 不使用外键约束
|
|
本项目采用无外键约束的数据库设计,通过应用层代码维护数据关联关系。
|
|
|
|
## 🎯 设计理由
|
|
|
|
### 1. 性能优化
|
|
- **减少约束检查**: 数据库不需要在每次插入/更新时检查外键约束
|
|
- **提高并发性**: 避免外键锁定导致的并发问题
|
|
- **加快批量操作**: 大批量数据导入时无需考虑外键顺序
|
|
|
|
### 2. 开发灵活性
|
|
- **表结构调整**: 修改表结构时不需要先删除外键约束
|
|
- **数据迁移**: 数据迁移和同步更加简单
|
|
- **测试便利**: 测试数据准备更加灵活
|
|
|
|
### 3. 分布式友好
|
|
- **微服务架构**: 不同微服务可以独立管理自己的数据表
|
|
- **数据分片**: 便于后期进行数据库分片和分布式部署
|
|
- **跨库关联**: 支持跨数据库的数据关联
|
|
|
|
### 4. 维护简化
|
|
- **避免级联问题**: 不会因为外键级联操作导致意外的数据删除
|
|
- **减少死锁**: 降低因外键约束导致的数据库死锁概率
|
|
- **简化备份恢复**: 数据备份和恢复时无需考虑外键依赖顺序
|
|
|
|
## 🔗 关联关系维护
|
|
|
|
### 代码层面维护
|
|
通过业务代码确保数据一致性:
|
|
|
|
```java
|
|
// 示例:创建对话时关联用户
|
|
@Service
|
|
public class ConversationService {
|
|
|
|
@Autowired
|
|
private UserService userService;
|
|
|
|
@Autowired
|
|
private ConversationMapper conversationMapper;
|
|
|
|
public Conversation createConversation(String userId, String title) {
|
|
// 1. 验证用户是否存在
|
|
User user = userService.getById(userId);
|
|
if (user == null) {
|
|
throw new BusinessException("用户不存在");
|
|
}
|
|
|
|
// 2. 创建对话
|
|
Conversation conversation = new Conversation();
|
|
conversation.setUserId(userId); // 通过ID关联,不使用外键
|
|
conversation.setTitle(title);
|
|
|
|
conversationMapper.insert(conversation);
|
|
return conversation;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 数据一致性保证
|
|
1. **业务层验证**: 在业务逻辑中验证关联数据的存在性
|
|
2. **事务管理**: 使用数据库事务确保操作的原子性
|
|
3. **定期检查**: 定期运行数据一致性检查脚本
|
|
|
|
## 📊 表关联关系
|
|
|
|
### 主要关联关系
|
|
```
|
|
user (用户表)
|
|
├── conversation.user_id → user.id
|
|
├── emotion_record.user_id → user.id
|
|
├── community_post.user_id → user.id
|
|
└── user_stats.user_id → user.id
|
|
|
|
conversation (对话表)
|
|
├── message.conversation_id → conversation.id
|
|
└── coze_api_call.conversation_id → conversation.id
|
|
|
|
message (消息表)
|
|
├── emotion_analysis.message_id → message.id
|
|
└── coze_api_call.message_id → message.id
|
|
|
|
community_post (社区帖子表)
|
|
└── comment.post_id → community_post.id
|
|
|
|
growth_topic (成长课题表)
|
|
├── topic_interaction.topic_id → growth_topic.id
|
|
└── reward.topic_id → growth_topic.id
|
|
|
|
achievement (成就表)
|
|
└── reward.achievement_id → achievement.id
|
|
```
|
|
|
|
### 关联字段命名规范
|
|
- **外部ID字段**: 统一使用 `{table_name}_id` 格式
|
|
- **主键字段**: 统一使用 `id`
|
|
- **数据类型**: 统一使用 `VARCHAR(36)` 雪花算法ID
|
|
|
|
## 🛡️ 数据完整性保证
|
|
|
|
### 1. 应用层验证
|
|
```java
|
|
// 删除用户前检查关联数据
|
|
public void deleteUser(String userId) {
|
|
// 检查是否有关联的对话
|
|
if (conversationService.countByUserId(userId) > 0) {
|
|
throw new BusinessException("用户存在关联对话,无法删除");
|
|
}
|
|
|
|
// 检查是否有关联的情绪记录
|
|
if (emotionRecordService.countByUserId(userId) > 0) {
|
|
throw new BusinessException("用户存在情绪记录,无法删除");
|
|
}
|
|
|
|
userService.deleteById(userId);
|
|
}
|
|
```
|
|
|
|
### 2. 定期数据检查
|
|
```sql
|
|
-- 检查孤立的对话记录(用户不存在)
|
|
SELECT c.id, c.user_id
|
|
FROM conversation c
|
|
LEFT JOIN user u ON c.user_id = u.id
|
|
WHERE u.id IS NULL AND c.is_deleted = 0;
|
|
|
|
-- 检查孤立的消息记录(对话不存在)
|
|
SELECT m.id, m.conversation_id
|
|
FROM message m
|
|
LEFT JOIN conversation c ON m.conversation_id = c.id
|
|
WHERE c.id IS NULL AND m.is_deleted = 0;
|
|
```
|
|
|
|
### 3. 软删除策略
|
|
- 使用 `is_deleted` 字段标记删除状态
|
|
- 保留历史数据,避免硬删除导致的关联数据问题
|
|
- 定期清理真正需要删除的数据
|
|
|
|
## 🔧 最佳实践
|
|
|
|
### 1. 服务层设计
|
|
- **单一职责**: 每个服务只管理自己的数据表
|
|
- **接口调用**: 跨表查询通过服务接口调用
|
|
- **缓存策略**: 合理使用缓存减少跨表查询
|
|
|
|
### 2. 查询优化
|
|
- **索引设计**: 为关联字段创建合适的索引
|
|
- **批量查询**: 使用 IN 查询减少数据库访问次数
|
|
- **分页处理**: 大数据量查询时合理分页
|
|
|
|
### 3. 数据迁移
|
|
- **脚本化**: 数据迁移操作脚本化,可重复执行
|
|
- **验证机制**: 迁移后验证数据完整性
|
|
- **回滚方案**: 准备数据回滚方案
|
|
|
|
## ⚠️ 注意事项
|
|
|
|
### 1. 开发规范
|
|
- 严格按照关联关系进行数据操作
|
|
- 删除数据前必须检查关联关系
|
|
- 使用事务确保数据一致性
|
|
|
|
### 2. 监控告警
|
|
- 监控孤立数据的产生
|
|
- 定期检查数据一致性
|
|
- 异常情况及时告警
|
|
|
|
### 3. 文档维护
|
|
- 及时更新关联关系文档
|
|
- 记录数据操作规范
|
|
- 维护数据字典
|
|
|
|
---
|
|
|
|
**设计原则**: 简单、高效、可维护
|
|
**实施策略**: 代码约束 + 定期检查
|
|
**适用场景**: 微服务架构 + 高并发系统
|