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!** 🎯
|