10 KiB
10 KiB
导入速度优化方案对比
📊 当前性能瓶颈(已分析)
每条记录的操作耗时
| 步骤 | 操作 | 当前耗时 | 优化后 | 说明 |
|---|---|---|---|---|
| 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:简化班级处理(推荐)⭐⭐⭐
当前逻辑(复杂但安全)
// 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
优化方案:直接覆盖(快但简单)
// 不查询,直接操作
// 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:关闭详细日志
当前日志
logger.info("正在更新用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.updateUser(u);
logger.info("用户更新成功: 信息编号={}", infoNo);
logger.info("正在处理用户班级: 信息编号={}, 班级={}", infoNo, className);
每条记录: 3-4条日志
优化方案
// 只在出错时记录日志
// logger.info("正在更新用户: 信息编号={}, 姓名={}", infoNo, prisonerName);
userMapper.updateUser(u);
// logger.info("用户更新成功: 信息编号={}", infoNo);
优点:
- ✅ 减少日志IO
- ✅ 减少字符串拼接
缺点:
- ⚠️ 调试困难
- ⚠️ 无法追踪进度
节省时间: 每条约10-20ms
方案C:进一步减少进度更新
当前配置
int updateInterval = 10; // 每10条更新一次
可选配置
// 选项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:批量操作(最大优化)
当前方式:逐条处理
for (SysUser user : userList) {
selectUserById(userId); // 500ms
updateUser(user); // 100ms (修复后)
selectStudentClassList(...); // 500ms
updateStudentClass(...); // 1000ms
}
// 每条总计:~2100ms
// 100条总计:~3.5分钟
批量方式
// 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:立即执行(已完成)
-
✅ 修复Mapper重复字段
- 预期提升:15倍
- ⚠️ 必须重新编译和重启
-
✅ 调整进度更新频率
- 从5改为10
- 略微提升
-
⚠️ 执行数据库索引SQL
- 文件:
optimize_import_performance_safe.sql - 预期提升:查询速度提升2-5倍
- 文件:
阶段2:测试后决定
先重新编译和重启,测试效果。
如果速度已满意: 不需要进一步优化
如果还需要更快: 考虑以下方案
-
简化班级处理(推荐)
- 节省时间:每条500ms
- 风险:低
- 实现:修改
StudyClassUserServiceImpl.java
-
关闭详细日志(可选)
- 节省时间:每条20ms
- 风险:中(调试困难)
- 实现:注释logger语句
阶段3:终极优化(可选)
批量操作改造
- 需要大量代码修改
- 性能提升最大
- 实现复杂度高
- 建议数据量>1000时考虑
🎯 具体实现:简化班级处理
如果需要实现"简化班级处理",修改代码如下:
// 文件: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方法:
<!-- 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>
⚠️ 重要提醒
优先级
- 最高优先级: 重新编译和重启(修复Mapper)
- 高优先级: 执行数据库索引SQL
- 中优先级: 简化班级处理
- 低优先级: 其他优化
建议流程
- ✅ 修复Mapper(已完成)
- ⚠️ 重新编译和重启
- ⚠️ 执行索引SQL
- ✅ 测试导入100条数据
- 📊 评估速度是否满意
- 💡 如需进一步优化,再考虑其他方案
📋 快速决策表
如果修复后速度满意(<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! 🎯