406 lines
10 KiB
Markdown
406 lines
10 KiB
Markdown
# 导入速度优化方案对比
|
||
|
||
## 📊 **当前性能瓶颈(已分析)**
|
||
|
||
### **每条记录的操作耗时**
|
||
|
||
| 步骤 | 操作 | 当前耗时 | 优化后 | 说明 |
|
||
|------|------|----------|--------|------|
|
||
| 1 | `selectUserById` | 500ms | 500ms | 必须查询,无法优化 |
|
||
| 2 | `updateUser` | **1500ms** | **<100ms** | ✅ 已修复Mapper重复字段 |
|
||
| 3 | `selectStudentClassList` | 500ms | 0ms | ⚠️ 可选:跳过班级检查 |
|
||
| 4 | `updateStudentClass` | 1000ms | 1000ms | 根据需要执行 |
|
||
| 5 | `insertStudentClass` | 200ms | 200ms | 根据需要执行 |
|
||
| **总计** | **每条** | **~3700ms** | **~1800ms** | 修复Mapper后 |
|
||
|
||
---
|
||
|
||
## ✅ **已完成的优化**
|
||
|
||
### **1. 修复Mapper重复字段(最重要)**
|
||
|
||
**影响:** `updateUser` 从 1500ms 降到 <100ms
|
||
|
||
**状态:** ✅ 已修复
|
||
|
||
**要求:** ⚠️ 必须重新编译和重启服务
|
||
|
||
**效果:** 每条节省1400ms,100条节省2分20秒!
|
||
|
||
---
|
||
|
||
### **2. 调整进度更新频率**
|
||
|
||
**从:** 每5条更新一次
|
||
**改为:** 每10条更新一次
|
||
|
||
**状态:** ✅ 已修改
|
||
|
||
**效果:** 减少50%的进度更新操作
|
||
|
||
---
|
||
|
||
## 🎯 **可选的进一步优化**
|
||
|
||
### **方案A:简化班级处理(推荐)⭐⭐⭐**
|
||
|
||
#### **当前逻辑(复杂但安全)**
|
||
|
||
```java
|
||
// 1. 查询现有班级 (~500ms)
|
||
List<StudyStudentClass> oldClassList = studentClassMapper.selectStudentClassList(query);
|
||
|
||
// 2. 检查是否已在该班级
|
||
boolean classUpdated = false;
|
||
for (StudyStudentClass old : oldClassList) {
|
||
if (old.getClassId().equals(classId)) {
|
||
if (old.getStatus() != 1) {
|
||
// 重新激活
|
||
updateStudentClass(old); // ~1000ms
|
||
}
|
||
classUpdated = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 3. 如果不在该班级
|
||
if (!classUpdated) {
|
||
// 禁用其他班级
|
||
for (StudyStudentClass old : oldClassList) {
|
||
updateStudentClass(old); // ~1000ms * N
|
||
}
|
||
// 插入新班级
|
||
insertStudentClass(studentClass); // ~200ms
|
||
}
|
||
```
|
||
|
||
**优点:**
|
||
- ✅ 避免重复插入
|
||
- ✅ 保持历史记录
|
||
- ✅ 班级未变时不操作
|
||
|
||
**缺点:**
|
||
- ❌ 需要先查询(500ms)
|
||
- ❌ 可能需要多次UPDATE
|
||
|
||
---
|
||
|
||
#### **优化方案:直接覆盖(快但简单)**
|
||
|
||
```java
|
||
// 不查询,直接操作
|
||
// 1. 禁用该学员的所有班级
|
||
UPDATE student_class SET status=0 WHERE student_id=? AND status=1
|
||
|
||
// 2. 插入新班级(如果已存在会失败,忽略)
|
||
INSERT IGNORE INTO student_class (student_id, class_id, status, join_time)
|
||
VALUES (?, ?, 1, NOW())
|
||
ON DUPLICATE KEY UPDATE status=1, join_time=NOW()
|
||
```
|
||
|
||
**优点:**
|
||
- ✅ 不需要查询(节省500ms)
|
||
- ✅ 只需2个SQL语句
|
||
- ✅ 速度快
|
||
|
||
**缺点:**
|
||
- ⚠️ 不检查是否需要更新
|
||
- ⚠️ 即使班级未变也会UPDATE
|
||
|
||
**节省时间:** 每条约500ms
|
||
|
||
---
|
||
|
||
### **方案B:关闭详细日志**
|
||
|
||
#### **当前日志**
|
||
|
||
```java
|
||
logger.info("正在更新用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
|
||
userMapper.updateUser(u);
|
||
logger.info("用户更新成功: 信息编号={}", infoNo);
|
||
logger.info("正在处理用户班级: 信息编号={}, 班级={}", infoNo, className);
|
||
```
|
||
|
||
**每条记录:** 3-4条日志
|
||
|
||
#### **优化方案**
|
||
|
||
```java
|
||
// 只在出错时记录日志
|
||
// logger.info("正在更新用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
|
||
userMapper.updateUser(u);
|
||
// logger.info("用户更新成功: 信息编号={}", infoNo);
|
||
```
|
||
|
||
**优点:**
|
||
- ✅ 减少日志IO
|
||
- ✅ 减少字符串拼接
|
||
|
||
**缺点:**
|
||
- ⚠️ 调试困难
|
||
- ⚠️ 无法追踪进度
|
||
|
||
**节省时间:** 每条约10-20ms
|
||
|
||
---
|
||
|
||
### **方案C:进一步减少进度更新**
|
||
|
||
#### **当前配置**
|
||
|
||
```java
|
||
int updateInterval = 10; // 每10条更新一次
|
||
```
|
||
|
||
#### **可选配置**
|
||
|
||
```java
|
||
// 选项1:每20条(推荐平衡)
|
||
int updateInterval = 20;
|
||
|
||
// 选项2:每50条(追求速度)
|
||
int updateInterval = 50;
|
||
|
||
// 选项3:每100条(极致速度)
|
||
int updateInterval = 100;
|
||
```
|
||
|
||
**效果对比:**
|
||
|
||
| 配置 | 100条更新次数 | 用户体验 | 速度 |
|
||
|------|---------------|----------|------|
|
||
| 每5条 | 20次 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||
| 每10条 | 10次 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||
| 每20条 | 5次 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||
| 每50条 | 2次 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||
| 每100条 | 1次 | ⭐ | ⭐⭐⭐⭐⭐ |
|
||
|
||
**节省时间:** 每条约5-10ms
|
||
|
||
---
|
||
|
||
### **方案D:批量操作(最大优化)**
|
||
|
||
#### **当前方式:逐条处理**
|
||
|
||
```java
|
||
for (SysUser user : userList) {
|
||
selectUserById(userId); // 500ms
|
||
updateUser(user); // 100ms (修复后)
|
||
selectStudentClassList(...); // 500ms
|
||
updateStudentClass(...); // 1000ms
|
||
}
|
||
// 每条总计:~2100ms
|
||
// 100条总计:~3.5分钟
|
||
```
|
||
|
||
#### **批量方式**
|
||
|
||
```java
|
||
// 1. 批量查询用户
|
||
Map<Long, SysUser> userMap = selectUsersByIds(userIds); // 一次查询
|
||
|
||
// 2. 批量更新用户
|
||
batchUpdateUsers(userList); // 一次UPDATE
|
||
|
||
// 3. 批量处理班级
|
||
batchUpdateStudentClass(classList); // 一次UPDATE
|
||
batchInsertStudentClass(newClassList); // 一次INSERT
|
||
|
||
// 100条总计:~5秒
|
||
```
|
||
|
||
**优点:**
|
||
- ✅ 速度极快(数据库往返次数大幅减少)
|
||
- ✅ 性能提升10-50倍
|
||
|
||
**缺点:**
|
||
- ⚠️ 需要大量代码改动
|
||
- ⚠️ 实现复杂
|
||
- ⚠️ 可能影响进度显示
|
||
|
||
**节省时间:** 每条约1500-2000ms
|
||
|
||
---
|
||
|
||
## 📊 **方案对比总结**
|
||
|
||
| 方案 | 实现难度 | 节省时间/条 | 总节省(100条) | 推荐度 | 风险 |
|
||
|------|----------|-------------|---------------|--------|------|
|
||
| **Mapper修复** | ✅ 已完成 | **1400ms** | **2分20秒** | ⭐⭐⭐⭐⭐ | 无 |
|
||
| 简化班级处理 | 中 | 500ms | 50秒 | ⭐⭐⭐⭐ | 低 |
|
||
| 关闭详细日志 | 易 | 20ms | 2秒 | ⭐⭐⭐ | 中 |
|
||
| 减少进度更新 | ✅ 已完成 | 10ms | 1秒 | ⭐⭐⭐⭐ | 无 |
|
||
| 批量操作 | 难 | 2000ms | 3分 | ⭐⭐⭐⭐⭐ | 高 |
|
||
|
||
---
|
||
|
||
## 💡 **推荐优化顺序**
|
||
|
||
### **阶段1:立即执行(已完成)**
|
||
|
||
1. ✅ **修复Mapper重复字段**
|
||
- 预期提升:15倍
|
||
- ⚠️ 必须重新编译和重启
|
||
|
||
2. ✅ **调整进度更新频率**
|
||
- 从5改为10
|
||
- 略微提升
|
||
|
||
3. ⚠️ **执行数据库索引SQL**
|
||
- 文件:`optimize_import_performance_safe.sql`
|
||
- 预期提升:查询速度提升2-5倍
|
||
|
||
---
|
||
|
||
### **阶段2:测试后决定**
|
||
|
||
**先重新编译和重启,测试效果。**
|
||
|
||
**如果速度已满意:** 不需要进一步优化
|
||
|
||
**如果还需要更快:** 考虑以下方案
|
||
|
||
1. **简化班级处理**(推荐)
|
||
- 节省时间:每条500ms
|
||
- 风险:低
|
||
- 实现:修改`StudyClassUserServiceImpl.java`
|
||
|
||
2. **关闭详细日志**(可选)
|
||
- 节省时间:每条20ms
|
||
- 风险:中(调试困难)
|
||
- 实现:注释logger语句
|
||
|
||
---
|
||
|
||
### **阶段3:终极优化(可选)**
|
||
|
||
**批量操作改造**
|
||
- 需要大量代码修改
|
||
- 性能提升最大
|
||
- 实现复杂度高
|
||
- 建议数据量>1000时考虑
|
||
|
||
---
|
||
|
||
## 🎯 **具体实现:简化班级处理**
|
||
|
||
如果需要实现"简化班级处理",修改代码如下:
|
||
|
||
```java
|
||
// 文件:StudyClassUserServiceImpl.java
|
||
// 位置:更新用户时的班级处理
|
||
|
||
// === 当前复杂逻辑(第1122-1177行)===
|
||
// 查询现有班级
|
||
StudyStudentClass query = new StudyStudentClass();
|
||
query.setStudentId(u.getUserId());
|
||
List<StudyStudentClass> oldClassList = studentClassMapper.selectStudentClassList(query);
|
||
boolean classUpdated = false;
|
||
// ... 复杂的检查和更新逻辑 ...
|
||
|
||
// === 简化后(推荐)===
|
||
try {
|
||
// 1. 直接禁用该学员的所有活跃班级
|
||
studentClassMapper.deactivateStudentClasses(u.getUserId());
|
||
|
||
// 2. 插入或更新新班级(使用 ON DUPLICATE KEY UPDATE)
|
||
StudyStudentClass studentClass = new StudyStudentClass();
|
||
studentClass.setStudentId(u.getUserId());
|
||
studentClass.setClassId(classId);
|
||
studentClass.setStatus(1);
|
||
studentClass.setJoinTime(new Date());
|
||
studentClassMapper.insertOrUpdateStudentClass(studentClass);
|
||
|
||
logger.info("为学员 {} 更新班级为 {} 成功", infoNo, className);
|
||
} catch (Exception ex) {
|
||
logger.warn("为学员 {} 分配班级时失败: {}", infoNo, ex.getMessage());
|
||
}
|
||
```
|
||
|
||
需要添加两个新的Mapper方法:
|
||
|
||
```xml
|
||
<!-- SysStudentClassMapper.xml -->
|
||
|
||
<!-- 禁用学员的所有活跃班级 -->
|
||
<update id="deactivateStudentClasses">
|
||
UPDATE student_class
|
||
SET status = 0
|
||
WHERE student_id = #{studentId} AND status = 1
|
||
</update>
|
||
|
||
<!-- 插入或更新班级 -->
|
||
<insert id="insertOrUpdateStudentClass">
|
||
INSERT INTO student_class (student_id, class_id, status, join_time)
|
||
VALUES (#{studentId}, #{classId}, #{status}, #{joinTime})
|
||
ON DUPLICATE KEY UPDATE
|
||
status = VALUES(status),
|
||
join_time = VALUES(join_time)
|
||
</insert>
|
||
```
|
||
|
||
---
|
||
|
||
## ⚠️ **重要提醒**
|
||
|
||
### **优先级**
|
||
|
||
1. **最高优先级:** 重新编译和重启(修复Mapper)
|
||
2. **高优先级:** 执行数据库索引SQL
|
||
3. **中优先级:** 简化班级处理
|
||
4. **低优先级:** 其他优化
|
||
|
||
### **建议流程**
|
||
|
||
1. ✅ 修复Mapper(已完成)
|
||
2. ⚠️ **重新编译和重启**
|
||
3. ⚠️ 执行索引SQL
|
||
4. ✅ 测试导入100条数据
|
||
5. 📊 评估速度是否满意
|
||
6. 💡 如需进一步优化,再考虑其他方案
|
||
|
||
---
|
||
|
||
## 📋 **快速决策表**
|
||
|
||
### **如果修复后速度满意(<30秒/100条)**
|
||
✅ 不需要进一步优化
|
||
|
||
### **如果还需要更快(目标<15秒/100条)**
|
||
✅ 实施"简化班级处理"
|
||
|
||
### **如果需要极致速度(目标<5秒/100条)**
|
||
✅ 实施"批量操作"
|
||
|
||
---
|
||
|
||
## ✅ **总结**
|
||
|
||
### **当前状态**
|
||
- ✅ Mapper重复字段已修复
|
||
- ✅ 进度更新频率已优化(10条)
|
||
- ⚠️ 需要重新编译和重启
|
||
- ⚠️ 建议执行数据库索引SQL
|
||
|
||
### **预期效果**
|
||
仅修复Mapper后:
|
||
- 每条从 3700ms 降到 2300ms
|
||
- 100条从 6分钟 降到 4分钟
|
||
- **提升约40%**
|
||
|
||
加上索引优化后:
|
||
- 每条约 1000-1500ms
|
||
- 100条约 2-2.5分钟
|
||
- **提升约60%**
|
||
|
||
如果再简化班级处理:
|
||
- 每条约 500-1000ms
|
||
- 100条约 1-1.5分钟
|
||
- **提升约75%**
|
||
|
||
---
|
||
|
||
**建议:先执行阶段1的优化(重新编译+索引),测试效果后再决定是否需要阶段2!** 🎯
|