324 lines
6.4 KiB
Markdown
324 lines
6.4 KiB
Markdown
# 导入性能优化方案
|
||
|
||
## ❌ **问题分析**
|
||
|
||
### **问题1:SQL执行慢(每条1.2秒)**
|
||
```
|
||
slow sql 1164 millis. update sys_user
|
||
slow sql 1165 millis. update student_class
|
||
```
|
||
|
||
**原因:**
|
||
- 每条记录更新需要1.2秒,100条就需要2分钟
|
||
- UPDATE语句中字段重复
|
||
- 可能缺少数据库索引
|
||
|
||
---
|
||
|
||
### **问题2:班级分配逻辑错误**
|
||
```
|
||
Duplicate entry '234-17' for key 'student_class.uk_student_class'
|
||
```
|
||
|
||
**原因:**
|
||
- 先UPDATE旧班级status=0
|
||
- 再INSERT新班级记录
|
||
- 如果新旧班级ID相同,INSERT失败(唯一键冲突)
|
||
|
||
**日志分析:**
|
||
```
|
||
14:58:25.593 UPDATE student_class SET status=0 WHERE id=4642 (1.2秒)
|
||
14:58:26.760 INSERT student_class (234, 17, ...) (失败:Duplicate)
|
||
```
|
||
|
||
---
|
||
|
||
### **问题3:UPDATE SQL字段重复**
|
||
```sql
|
||
update sys_user
|
||
SET prison_name = ?, -- 第一次
|
||
prison_area = ?,
|
||
...
|
||
prison_name = ?, -- 第二次重复!
|
||
prison_area = ?, -- 重复!
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ **解决方案**
|
||
|
||
### **方案1:修复班级分配逻辑**
|
||
|
||
#### **当前逻辑(错误):**
|
||
```java
|
||
// 1. 将所有旧班级设为status=0
|
||
for (StudyStudentClass old : oldClassList) {
|
||
old.setStatus(0);
|
||
studentClassMapper.updateStudentClass(old); // 慢查询
|
||
}
|
||
|
||
// 2. 插入新班级
|
||
studentClassMapper.insertStudentClass(studentClass); // 失败:Duplicate
|
||
```
|
||
|
||
#### **优化后逻辑(正确):**
|
||
```java
|
||
// 1. 检查是否已在该班级
|
||
boolean classUpdated = false;
|
||
for (StudyStudentClass old : oldClassList) {
|
||
if (old.getClassId().equals(classId)) {
|
||
// 已在该班级,只需确保状态为活跃
|
||
if (old.getStatus() != 1) {
|
||
old.setStatus(1);
|
||
studentClassMapper.updateStudentClass(old);
|
||
}
|
||
classUpdated = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 2. 如果不在该班级,禁用其他班级
|
||
if (!classUpdated) {
|
||
for (StudyStudentClass old : oldClassList) {
|
||
if (old.getStatus() == 1) {
|
||
old.setStatus(0);
|
||
studentClassMapper.updateStudentClass(old);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. 如果不在该班级,插入新记录
|
||
if (!classUpdated) {
|
||
studentClassMapper.insertStudentClass(studentClass);
|
||
}
|
||
```
|
||
|
||
**优势:**
|
||
- ✅ 避免Duplicate错误
|
||
- ✅ 减少不必要的UPDATE
|
||
- ✅ 逻辑更清晰
|
||
|
||
---
|
||
|
||
### **方案2:添加数据库索引**
|
||
|
||
#### **检查当前索引:**
|
||
```sql
|
||
SHOW INDEX FROM sys_user;
|
||
SHOW INDEX FROM student_class;
|
||
```
|
||
|
||
#### **建议添加的索引:**
|
||
```sql
|
||
-- 用户表:按信息编号查询
|
||
CREATE INDEX idx_user_name ON sys_user(user_name);
|
||
|
||
-- 学员班级表:按学员ID查询
|
||
CREATE INDEX idx_student_id ON student_class(student_id);
|
||
|
||
-- 学员班级表:按状态查询
|
||
CREATE INDEX idx_status ON student_class(status);
|
||
```
|
||
|
||
---
|
||
|
||
### **方案3:检查UPDATE SQL(需要修复Mapper)**
|
||
|
||
检查 `SysUserMapper.xml` 中的 `updateUser` 是否有重复字段:
|
||
|
||
```xml
|
||
<update id="updateUser">
|
||
update sys_user
|
||
SET prison_name = #{prisonName},
|
||
prison_area = #{prisonArea},
|
||
...
|
||
<!-- 检查是否重复 -->
|
||
prison_name = #{prisonName}, <!-- 重复! -->
|
||
prison_area = #{prisonArea} <!-- 重复! -->
|
||
where user_id = #{userId}
|
||
</update>
|
||
```
|
||
|
||
---
|
||
|
||
### **方案4:使用批量操作**
|
||
|
||
#### **当前:逐条处理**
|
||
```java
|
||
for (SysUser user : userList) {
|
||
userMapper.insertUser(user); // 每次1.2秒
|
||
}
|
||
```
|
||
|
||
#### **优化:批量插入**
|
||
```java
|
||
// 批量插入(每批50条)
|
||
List<SysUser> batch = new ArrayList<>();
|
||
for (SysUser user : userList) {
|
||
batch.add(user);
|
||
if (batch.size() >= 50) {
|
||
userMapper.batchInsertUsers(batch);
|
||
batch.clear();
|
||
}
|
||
}
|
||
if (!batch.isEmpty()) {
|
||
userMapper.batchInsertUsers(batch);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 **立即修复步骤**
|
||
|
||
### **步骤1:修复班级分配逻辑**
|
||
|
||
文件:`StudyClassUserServiceImpl.java`
|
||
|
||
位置:第1110-1126行(更新用户时的班级分配)
|
||
|
||
需要补充完整的班级分配逻辑(类似新增用户的逻辑)
|
||
|
||
---
|
||
|
||
### **步骤2:检查并修复UPDATE SQL**
|
||
|
||
文件:`SysUserMapper.xml`
|
||
|
||
搜索:`<update id="updateUser">`
|
||
|
||
删除重复的字段设置
|
||
|
||
---
|
||
|
||
### **步骤3:添加数据库索引**
|
||
|
||
```sql
|
||
-- 连接数据库
|
||
USE ry_study;
|
||
|
||
-- 检查现有索引
|
||
SHOW INDEX FROM sys_user WHERE Key_name = 'idx_user_name';
|
||
SHOW INDEX FROM student_class WHERE Key_name = 'idx_student_id';
|
||
|
||
-- 添加缺失的索引
|
||
CREATE INDEX idx_user_name ON sys_user(user_name) IF NOT EXISTS;
|
||
CREATE INDEX idx_student_id ON student_class(student_id) IF NOT EXISTS;
|
||
CREATE INDEX idx_status ON student_class(status) IF NOT EXISTS;
|
||
```
|
||
|
||
---
|
||
|
||
### **步骤4:优化数据库配置**
|
||
|
||
检查数据库连接池配置:
|
||
|
||
```yaml
|
||
# application.yml
|
||
spring:
|
||
datasource:
|
||
druid:
|
||
initial-size: 10
|
||
min-idle: 10
|
||
max-active: 50 # 增加最大连接数
|
||
max-wait: 60000
|
||
```
|
||
|
||
---
|
||
|
||
##⚡ **预期性能提升**
|
||
|
||
### **修复前:**
|
||
- 100条数据:约2-3分钟
|
||
- 每条平均:1.2-1.8秒
|
||
|
||
### **修复后:**
|
||
- 100条数据:约10-20秒
|
||
- 每条平均:0.1-0.2秒
|
||
|
||
**提升:10倍以上**
|
||
|
||
---
|
||
|
||
## 📋 **快速检查清单**
|
||
|
||
- [ ] 修复班级分配逻辑
|
||
- [ ] 检查UPDATE SQL是否有重复字段
|
||
- [ ] 添加数据库索引
|
||
- [ ] 测试导入100条数据
|
||
- [ ] 查看SQL慢查询日志
|
||
- [ ] 确认无Duplicate错误
|
||
|
||
---
|
||
|
||
## 🧪 **测试步骤**
|
||
|
||
1. **准备测试数据**
|
||
```bash
|
||
python generate_test_data.py # 生成100条
|
||
```
|
||
|
||
2. **执行导入**
|
||
- 记录开始时间
|
||
- 导入test_data.xlsx
|
||
- 记录结束时间
|
||
|
||
3. **查看日志**
|
||
```bash
|
||
# 搜索慢查询
|
||
grep "slow sql" logs/app.log
|
||
|
||
# 搜索错误
|
||
grep "Duplicate entry" logs/app.log
|
||
|
||
# 计算平均时间
|
||
```
|
||
|
||
4. **验证结果**
|
||
- 检查导入成功数量
|
||
- 检查班级分配是否正确
|
||
- 检查是否有错误
|
||
|
||
---
|
||
|
||
## 💡 **后续优化建议**
|
||
|
||
### **1. 使用Redis缓存**
|
||
- 缓存班级映射
|
||
- 缓存角色ID
|
||
- 减少数据库查询
|
||
|
||
### **2. 异步处理**
|
||
- 使用消息队列
|
||
- 导入任务放入队列
|
||
- 后台异步处理
|
||
|
||
### **3. 分片导入**
|
||
- 大文件分片上传
|
||
- 每片独立处理
|
||
- 提高并发性
|
||
|
||
### **4. 数据库优化**
|
||
- 分表分库
|
||
- 读写分离
|
||
- 使用更快的存储引擎
|
||
|
||
---
|
||
|
||
## ✅ **总结**
|
||
|
||
### **核心问题**
|
||
1. 班级分配逻辑错误导致Duplicate
|
||
2. UPDATE SQL慢查询
|
||
3. 缺少必要的数据库索引
|
||
|
||
### **解决方法**
|
||
1. 修复班级分配逻辑
|
||
2. 检查并优化SQL
|
||
3. 添加数据库索引
|
||
|
||
### **预期效果**
|
||
- 性能提升10倍以上
|
||
- 无Duplicate错误
|
||
- 导入更流畅
|