guoyu/Test/备份/_已清理文件备份_周六 22512/md/导入速度慢问题完整解决方案.md

9.8 KiB
Raw Blame History

导入速度慢问题完整解决方案

🔴 问题现状

从日志分析,发现系统存在严重性能问题:

查询性能问题

查询 单次耗时 影响
countUserRoleByUserId 0.58秒 每个用户查询2次
selectRolePermissionByUserId 0.58秒 每个用户查询2次
selectCoursewareList 0.78秒 课程页面加载慢
selectLearningDetailList 0.58秒 学习记录加载慢

影响

  • 导入 100 个用户预计需要 10-15 分钟(理论应该 1-2 分钟)
  • 更新 10 个用户需要 2-3 分钟
  • 页面加载需要 5-10 秒

🎯 根本原因

原因1冗余索引导致写入慢最严重

问题: student_class 表有冗余索引

 uk_student_class (student_id, class_id)  -- 已覆盖 student_id
 idx_student_id (student_id)              -- 完全冗余!

影响:

  • 每次 INSERT/UPDATE 需要维护多个冗余索引
  • 写入速度降低 50%+
  • 系统整体变慢

证据:

  • 您之前添加了索引后系统变慢
  • 您打开了 fix_redundant_indexes.sql 但还没执行

原因2查询缺少必要的索引

问题Asys_user_role 表

-- 慢查询
SELECT COUNT(1) FROM sys_user_role WHERE user_id = ?
-- 0.58秒!

-- 原因:缺少 user_id 索引

问题Bcourseware 表

-- 慢查询
SELECT * FROM courseware WHERE course_id = ?
-- 0.78秒!

-- 原因:缺少或未使用 course_id 索引

问题Clearning_detail 表

-- 慢查询
SELECT * FROM learning_detail WHERE student_id = ? AND course_id = ?
-- 0.58秒!

-- 原因:缺少复合索引 (student_id, course_id)

完整解决方案

步骤1删除冗余索引最关键

在 Navicat 中执行:

文件log/Sql/fix_redundant_indexes.sql

效果:

  • 删除 student_class.idx_student_id(冗余)
  • 写入速度提升 50%+
  • 导入速度提升 2-3 倍

预计时间: 1-2 分钟


步骤2优化查询索引

在 Navicat 中执行:

文件log/Sql/fix_slow_queries.sql

效果:

  • 添加 sys_user_role(user_id) 索引
  • 添加 sys_user_role(role_id) 索引
  • 添加 sys_role(del_flag) 索引
  • 添加 courseware(course_id) 索引
  • 添加 learning_detail(student_id, course_id) 复合索引
  • 查询速度从 0.5-0.8秒 降低到 10-50ms

预计时间: 2-3 分钟


步骤3重启应用服务

# 停止服务
# 启动服务

步骤4验证效果

验证1查询速度

在 Navicat 中测试:

-- 测试角色查询(应该 <50ms
SET @start = NOW(6);
SELECT COUNT(*) FROM sys_user_role WHERE user_id = 455;
SET @end = NOW(6);
SELECT TIMESTAMPDIFF(MICROSECOND, @start, @end)/1000 AS '耗时(ms)';

-- 测试课程查询(应该 <50ms
SET @start = NOW(6);
SELECT COUNT(*) FROM courseware WHERE course_id = 6;
SET @end = NOW(6);
SELECT TIMESTAMPDIFF(MICROSECOND, @start, @end)/1000 AS '耗时(ms)';

-- 测试学习详情查询(应该 <50ms
SET @start = NOW(6);
SELECT COUNT(*) FROM learning_detail WHERE student_id = 9999 AND course_id = 6;
SET @end = NOW(6);
SELECT TIMESTAMPDIFF(MICROSECOND, @start, @end)/1000 AS '耗时(ms)';

验证2导入速度

  • 导入 100 条数据
  • 修复前: 10-15 分钟
  • 修复后: 1-2 分钟

验证3更新速度

  • 更新 10 条数据
  • 修复前: 2-3 分钟
  • 修复后: 10-30 秒

验证4页面加载速度

  • 打开用户列表页面
  • 修复前: 5-10 秒
  • 修复后: <2 秒

📊 优化前后对比

查询性能

查询类型 优化前 优化后 提升
角色查询 580ms 10-50ms 10-50倍
课程查询 780ms 10-50ms 15-70倍
学习详情 580ms 10-50ms 10-50倍

导入性能

场景 优化前 优化后 提升
导入100条 10-15分钟 1-2分钟 5-10倍
更新10条 2-3分钟 10-30秒 4-10倍
页面加载 5-10秒 <2秒 3-5倍

🔍 技术原理

为什么冗余索引会慢?

-- 有冗余索引时:
INSERT INTO student_class (student_id, class_id) VALUES (1, 2);

-- MySQL 需要更新:
 PRIMARY KEY
 uk_student_class (student_id, class_id)  -- 已经包含 student_id
 idx_student_id (student_id)              -- 冗余!需要额外更新

-- 结果:写入时间增加 50%+

为什么需要复合索引?

-- 查询:
WHERE student_id = ? AND course_id = ?

-- 单独索引:
idx_student_id (student_id)      -- 只能用于 student_id
idx_course_id (course_id)        -- 只能用于 course_id

-- 复合索引:
idx_student_course (student_id, course_id)  -- 同时用于两个条件!速度快!

为什么 uk_student_class 可以替代 idx_student_id

MySQL 最左前缀原则:

-- uk_student_class (student_id, class_id) 可以用于:
WHERE student_id = ?                     使用索引
WHERE student_id = ? AND class_id = ?    使用索引
WHERE class_id = ?                       不使用索引

-- 所以不需要单独的 idx_student_id

⚠️ 注意事项

1. 备份数据库

执行 SQL 脚本前,建议先备份:

-- 导出备份
mysqldump -u root -p study > study_backup_$(date +%Y%m%d).sql

2. 执行顺序

必须按照以下顺序执行:

  1. 先执行 fix_redundant_indexes.sql(删除冗余索引)
  2. 再执行 fix_slow_queries.sql(添加必要索引)
  3. 最后 重启服务

不要反过来!

3. 执行时间

  • 冗余索引删除1-2 分钟
  • 查询索引优化2-3 分钟
  • 总计3-5 分钟

4. 服务影响

  • 执行过程中可能短暂锁表
  • 建议在低峰期执行
  • 或者先停止服务再执行

📋 完整操作清单

准备阶段

  • 备份数据库
  • 通知用户维护时间
  • 准备回滚脚本(如果需要)

执行阶段

  • 在 Navicat 中打开 fix_redundant_indexes.sql
  • 执行脚本,确认删除冗余索引
  • 在 Navicat 中打开 fix_slow_queries.sql
  • 执行脚本,确认添加必要索引
  • 查看脚本输出,确认优化成功
  • 重启应用服务

验证阶段

  • 测试角色查询速度(<50ms
  • 测试课程查询速度(<50ms
  • 测试学习详情查询速度(<50ms
  • 导入 100 条数据测试1-2分钟
  • 更新 10 条数据测试10-30秒
  • 打开页面测试加载速度(<2秒

确认阶段

  • 检查应用日志,无异常
  • 检查数据库慢查询日志
  • 用户确认速度提升
  • 记录优化结果

🚨 如果优化后仍然慢

检查1是否执行了两个脚本

-- 检查冗余索引是否已删除
SELECT INDEX_NAME 
FROM information_schema.STATISTICS 
WHERE TABLE_SCHEMA = 'study' 
AND TABLE_NAME = 'student_class' 
AND INDEX_NAME = 'idx_student_id';

-- 应该返回空结果!

检查2是否重启了服务

  • MySQL 连接可能缓存了旧的查询计划
  • 重启服务可以强制重新规划

检查3数据库服务器资源

-- 检查数据库连接数
SHOW PROCESSLIST;

-- 检查慢查询
SHOW VARIABLES LIKE 'slow_query_log%';

-- 检查缓冲池
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';

检查4导入逻辑本身

如果查询已经很快,但导入仍然慢:

  • 检查是否有其他业务逻辑耗时
  • 检查是否有外部API调用
  • 检查是否有大量日志输出
  • 检查是否有文件IO操作

💡 长期优化建议

1. 定期分析表

-- 每周执行一次
ANALYZE TABLE sys_user;
ANALYZE TABLE sys_user_role;
ANALYZE TABLE student_class;
ANALYZE TABLE courseware;
ANALYZE TABLE learning_detail;

2. 监控慢查询

启用慢查询日志:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 1秒以上算慢查询

3. 定期检查索引

-- 查找冗余索引
SELECT 
    table_name,
    index_name,
    GROUP_CONCAT(column_name ORDER BY seq_in_index) AS columns
FROM information_schema.statistics
WHERE table_schema = 'study'
GROUP BY table_name, index_name
ORDER BY table_name, index_name;

4. 优化批量操作

  • 导入时使用批量插入
  • 减少单条 INSERT改用 INSERT INTO ... VALUES (...), (...), (...)
  • 临时禁用索引,导入后重建

📞 获取帮助

如果按照上述步骤操作后仍有问题:

  1. 检查应用日志

    Study-Vue-redis/logs/
    
  2. 检查数据库慢查询日志

    SHOW VARIABLES LIKE 'slow_query_log_file';
    
  3. 提供详细信息

    • 执行的 SQL 脚本输出
    • 测试查询的耗时
    • 导入测试的日志
    • 数据库版本和配置

总结

核心问题

  1. 冗余索引导致写入慢
  2. 缺少必要索引导致查询慢

解决方案

  1. 删除冗余索引(fix_redundant_indexes.sql
  2. 添加必要索引(fix_slow_queries.sql
  3. 重启服务

预期效果

  • 查询速度0.5-0.8秒 → 10-50ms(提升 10-70倍
  • 导入速度10-15分钟 → 1-2分钟(提升 5-10倍
  • 更新速度2-3分钟 → 10-30秒(提升 4-10倍
  • 页面加载5-10秒 → <2秒(提升 3-5倍

现在请按照步骤执行,应该能彻底解决速度问题! 🚀

关键:先删除冗余索引,再添加必要索引,最后重启服务! ⚠️