添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议 》和 《 阿里云开发者社区知识产权保护指引 》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单 进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

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 错误

提示堆栈溢出。

image.png

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 总结

在无法控制递归深度的前提下,慎用递归,用循环替代。

解决Java整合minio错误:S3 API Request made to Console port. S3 Requests should be sent to API port 4n2nnvzgqrwxq Java程序员编程时需要混合面向对象思维和一般命令式编程的方法,能否完美地将两者结合起来完全得依靠编程人员的水准: 技能:任何人都能容易学会命令式编程 模式:有些人用“模式-模式”,举个例子,模式可以应用到任何地方,而且都可以归为某一类模式 心境:首先,要写个好的面向对象程序是比命令式程序难得多,你得花费一些功夫 但当Java程序员写SQL语句时,一切都不一样了。SQL是说明性语言而非面向对象或是命令式编程语言。在SQL中要写个查询语句是很简单的。但在Java里类似的语句却不容易,因为程序员不仅要反复考虑编程范式,而且也要考虑算法的问题。 下面是Java程序员在写SQL时常犯的10个错误 愿天堂没有BUG(公众号同名) 使用Java面向对象编写网络通信程序应用 Elasticsearch Java API Client 开发 手动部署Java Web环境(Alibaba Cloud Linux 2) 搭建Java Web开发环境(Anolis OS) RocketMQ中使用Java客户端发送消息和消费的应用