guoyu/Test/备份/_已清理文件备份_周六 22512/md/导入速度优化方案对比.md

10 KiB
Raw Blame History

导入速度优化方案对比

📊 当前性能瓶颈(已分析)

每条记录的操作耗时

步骤 操作 当前耗时 优化后 说明
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

状态: 已修复

要求: ⚠️ 必须重新编译和重启服务

效果: 每条节省1400ms100条节省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立即执行已完成

  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时考虑

🎯 具体实现:简化班级处理

如果需要实现"简化班级处理",修改代码如下:

// 文件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>

⚠️ 重要提醒

优先级

  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 🎯