1 背景
系统有很多的日志表,需要定期进行数据清理,根据配置保留N天。当前已经运行几年,日志数据量很大,如直接调用delete from 表名 where create_time < 清理截止时间,会导致数据库超大事务操作,影响数据库当前的正常运行。
采用递归的方式每次查询500条id集合后调用delete from where id in (500个id),直至查询结果为空或小于500条。同时查询不在serveice层进行,将一个大事务拆分成多个小事务进行。
1.1 主体方法
其中clearHistoryQueryPO中内置属性limit = 500。
private void clearTableHistory(ClearHistoryQueryPO clearHistoryQueryPO) {
Set<Long> ids = databaseClearService.listIds(clearHistoryQueryPO);
if(CollectionUtils.isEmpty(ids)) {
return;
}
databaseClearService.deleteByIds(new ClearHistoryDeletePO(clearHistoryQueryPO,ids));
LOGGER.debug("清理{}表日志,数量{}条", clearHistoryQueryPO.getTableName(), ids.size());
//如果结果集数量小于limit,说明已经没有匹配的数据,结束递归
if (ids.size() < clearHistoryQueryPO.getLimit()) {
return;
}
//递归查询
clearTableHistory(clearHistoryQueryPO);
}
1.2 listIds对应mapper配置
<!-- 根据时间查询limit条id集合-->
<select id="listIds" parameterType="com.xtt.hd.dao.po.ClearHistoryQueryPO" resultType="java.lang.Long">
<![CDATA[
select ${primaryKey} from ${tableName}
where create_time <= #{clearMaxTime}
limit #{limit}
]]>
</select>
1.3 deleteByIds对应mapper配置
<!-- 根据id集合删除数据-->
<delete id="deleteByIds" parameterType="com.xtt.hd.dao.po.ClearHistoryDeletePO">
delete from ${tableName}
where ${primaryKey} in
<foreach collection="ids" open="(" close=")" item="item" separator=",">
#{item}
</foreach>
</delete>
2 错误
提示堆栈溢出。
3 分析
通过查阅资料,
递归过深,栈帧数超出虚拟栈
深度
,虚拟机栈过多会引发java.lang.OutOfMemoryError异常。
本程序每天夜间定时执行,相当于每天清理一天的日志信息,不会太大。但是系统已运行几年,当首次执行时候,有些表的清理数据已经达到百万级别,那么就会导致递归太深。
4 修改
将递归调用改成循环调用。
private void clearTableHistory(ClearHistoryQueryPO clearHistoryQueryPO) {
Set<Long> ids = null;
while ((ids = databaseClearService.listIds(clearHistoryQueryPO)) !=null) {
if(ids.size() == 0) {
break;
}
databaseClearService.deleteByIds(new ClearHistoryDeletePO(clearHistoryQueryPO,ids));
LOGGER.debug("清理{}表日志,数量{}条", clearHistoryQueryPO.getTableName(), ids.size());
//如果结果集数量小于limit,说明已经没有匹配的数据,结束递归
if (ids.size() < clearHistoryQueryPO.getLimit()) {
break;
}
}
}
5 总结
在无法控制递归深度的前提下,慎用递归,用循环替代。