档案管理系统报表性能瓶颈通常出现在数据库IO上,特别是针对海量档案历史数据的查询。首先必须对慢查询进行精准定位与优化。
开启MySQL慢查询日志,设置阈值为1秒。在档案借阅统计报表中,通常涉及`archive_id`、`borrow_date`和`dept_id`的联合查询。
操作步骤:
EXPLAIN SELECT count() FROM t_borrow_record
WHERE borrow_date BETWEEN '2023-01-01' AND '2023-12-31'
AND dept_id = 'D001';
如果type为ALL或index,key为NULL,说明全表扫描,必须优化。
2.创建联合索引:根据最左前缀原则,创建覆盖索引。
-- 删除旧索引(如果存在)
DROP INDEX idx_borrow_date_dept ON t_borrow_record;
-- 创建联合索引,将高区分度的字段放在前面
CREATE INDEX idx_borrow_date_dept ON t_borrow_record(borrow_date, dept_id);
验证效果:再次执行EXPLAIN,type应变为range,key显示为idx_borrow_date_dept。
档案报表常包含多表关联(如档案表、分类表、机构表)。避免使用SELECT ,只查询报表所需字段。
优化前(低效):
SELECT FROM t_archives a
LEFT JOIN t_category c ON a.cat_id = c.id
LEFT JOIN t_dept d ON a.dept_id = d.id
WHERE a.status = 1;
优化后(高效):
SELECT a.id, a.title, a.create_time, c.cat_name, d.dept_name
FROM t_archives a
INNER JOIN t_category c ON a.cat_id = c.id
INNER JOIN t_dept d ON a.dept_id = d.id
WHERE a.status = 1;
将LEFT JOIN改为INNER JOIN(如果业务逻辑允许),并明确指定字段,减少数据传输量。
档案报表动辄导出十万级数据,同步处理会导致HTTP请求超时或服务器内存溢出(OOM)。必须采用异步任务+流式写入方案。

在Spring Boot项目中配置专用线程池,隔离报表导出任务,避免阻塞主线程。
@Configuration
public class ThreadPoolConfig {
@Bean("reportExecutor")
public Executor reportExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:根据CPU核心数设置,建议为4
executor.setCorePoolSize(4);
// 最大线程数:建议为10-20,避免过多上下文切换
executor.setMaxPoolSize(10);
// 队列容量:缓冲积压任务
executor.setQueueCapacity(200);
// 线程名称前缀
executor.setThreadNamePrefix("async-report-");
// 拒绝策略:由调用者线程执行,保证任务不丢失
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
使用阿里EasyExcel组件,避免将全量数据加载到内存。直接将查询结果流写入OutputStream。
@Service
public class ArchiveReportService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Async("reportExecutor")
public void exportArchiveLargeData(String sql, HttpServletResponse response) {
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("档案报表", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename=utf-8''" + fileName + ".xlsx");
// 直接写入输出流,不持有全部数据
EasyExcel.write(response.getOutputStream(), ArchiveData.class)
.sheet("档案明细")
.doWrite(() -> {
// 分页查询逻辑,这里演示直接流式查询
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ArchiveData.class));
});
} catch (IOException e) {
throw new RuntimeException("导出失败", e);
}
}
}
注意:对于百万级数据,需在SQL中增加分页逻辑,循环调用doWrite的分页方法,或者使用数据库游标(Cursor)进行流式读取。
档案首页看板或高频统计报表(如“今日归档数量”)实时性要求不高,但访问频率极高。引入Redis缓存是降低数据库负载最直接的手段。
确保RedisTemplate使用JSON序列化,避免JDK默认序列化导致的二进制乱码及存储浪费。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer替换默认序列化
Jackson2JsonRedisSerializer
在Service层方法上直接添加注解,实现声明式缓存。
@Service
public class StatisticsService {
// 缓存key名称,value为返回值
// unless = "result == null" 防止缓存空对象(穿透问题)
@Cacheable(value = "archiveStats", key = "'dailyCount'", unless = "result == null")
public Long getDailyArchiveCount() {
// 模拟耗时SQL查询
String sql = "SELECT count() FROM t_archives WHERE DATE(create_time) = CURDATE()";
return jdbcTemplate.queryForObject(sql, Long.class);
}
// 更新数据时清除缓存
@CacheEvict(value = "archiveStats", key = "'dailyCount'")
public void updateArchiveStatus(Long id, Integer status) {
String sql = "UPDATE t_archives SET status = ? WHERE id = ?";
jdbcTemplate.update(sql, status, id);
}
}
实操细节:设置合理的过期时间(TTL),例如在Redis配置类中自定义RedisCacheManager,设置默认过期时间为30分钟。
代码层面的优化若不配合运行时参数调优,性能提升依然有限。档案系统并发导出时,连接池和内存是最大瓶颈。
Spring Boot 2.x+默认使用HikariCP,需在application.yml中精细配置。
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
hikari:
最小空闲连接,保持与核心线程数一致
minimum-idle: 5
最大连接池大小,公式:(核心数 2) + 有效磁盘数
maximum-pool-size: 20
连接超时时间(毫秒),超过此时间未获取到连接则报错
connection-timeout: 30000
空闲连接存活最大时间(毫秒),默认600000(10分钟),建议调短回收资源
idle-timeout: 600000
连接最大生命周期(毫秒),建议设置小于数据库wait_timeout
max-lifetime: 1800000
连接测试查询,MySQL推荐SELECT 1
connection-test-query: SELECT 1
针对报表导出涉及大文件处理的特点,调整JVM启动参数。
推荐配置:
-server
初始化堆大小,避免频繁扩容抖动
-Xms2g
最大堆大小,建议不超过物理内存的80%
-Xmx2g
新生代大小,报表对象生命周期短,适当增大新生代
-Xmn1g
使用G1垃圾收集器,适合大内存低停顿场景
-XX:+UseG1GC
设置GC停顿时间目标(毫秒)
-XX:MaxGCPauseMillis=200
打开GC日志,用于后续分析OOM问题
-Xloggc:/var/log/app/gc.log
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
验证方法:在导出大报表时,使用`jstat -gcutil pid 1000`监控GC情况。如果Full GC频繁,需调大新生代或检查代码是否有内存泄漏。