使用Java程序一次分段读取所有数据(如海量数据)并计数处理
前段时间遇到一个问题,很简单就是定时任务删除数据库中三个月前的数据;无非就是delete...from...where;当时的需求要考虑这几个问题:
1.效率
2.一次读取全部
3.保留部分数据
先说一下当时的需求,删除三个月前的动态(团队动态),但有些团队的动态本身就很少,删除了影响前台数据量,所以只删除三个月内动态>200条的数据;
其实完全可以一个简单的delete from,count计数,但这对磁盘IO消耗很大;所以要在内存中处理;如何读取数据,计数是主要问题;
下面是当时实现代码
//定时删除团队动态public class AutoDelTeamDynamic { protected final static Log logger = LogFactory.getLog(AutoDelTeamDynamic. class ); public void executeInternal() { logger.warn( " autoDelTeamDynamic start! " ); Date d = new Date(); //当前时间 Date startDate = DateUtil.getDateInDayAgo(d, - 90 ); Date delDate = DateUtil.dateUtil2date(startDate, "yyyy-MM-dd" ); //三个月前 //三个月团队内动态数量大于200删除 Long id = 0l; String dynamics = "select id,team_id,create_time from g_team_dynamic where id > ? order by team_id asc,id desc limit 10000" ; while ( true ){ List<Map<String, Object>> result = Db.executeQuery(dynamics, new Object[]{id}, false ); if (result == null || result.size() < 10000 ){ break ; } Map<Long,Integer> map = new HashMap<Long, Integer>(); StringBuffer sb = new StringBuffer(); for (Map<String, Object> numMap : result){ id = (Long)numMap.get( "id" ); Long team_id = (Long)numMap.get( "team_id" ); Date create_time = (Date)numMap.get( "create_time" ); Date date = DateUtil.dateUtil2date(create_time, "yyyy-MM-dd" ); if (DateUtil.compareTwoDate(date, delDate) == 1 ){ if (map.containsKey(team_id)){ map.put(team_id, map.get(team_id) + 1 ); if (map.get(team_id) > 200 ){ sb.append(id).append( "," ); } } else { map.put(team_id, 1 ); } } } if (sb.length() > 0 ){ sb.deleteCharAt(sb.length()- 1 ); delDynamic(sb.toString()); } id--; } logger.warn( " autoDelTeamDynamic end! " ); } private void delDynamic(String ids){ String delsql = " DELETE FROM g_team_dynamic WHERE id in (" +ids+ ")" ; int a = Db.executeUpdate(delsql, null , false ); if (a <= 0 ){ logger.error( "删除每日任务记录失败!" +delsql); } } } |
以上代码是工程中的,只是一个思路问题;SQL语句是分段读取数据的,如何让分段读取能够读取到数据库中全部数据,这里用到了while(true),每次读取10000条,即只要数据库中数据还多于一万就一直向下执行,直到数据少于10000时结束;还有个问题就是分段读取数据的衔接,注意Long id=0,及SQL语句中的where条件,主键id之后被数据中读取数据重新赋值,一次循环执行SQL结束,id--,这样就可以将两次Limit的数据衔接上(id主键自增),如此执行,就可以一次读取到数据库中所有数据;
下面就是计数问题,在这里是使用Map计数的,每当发现一个team_id,判断Map中是否包含,不包含创建,计数1;包含则在原来个数上加一,如此计数,当team_id对应数据大于200,直接将数据库中记录id放入可删除字符串中;
最后就是根据主键删除记录,如此做是效率最高的删除操作,where条件放入in条件,一次传入所有需要删除记录的ID;
这样就可以实现需求;海量数据处理确实很难模拟,不在互联网公司很难接触到,一个删除操作就要如此麻烦,但我们都应该知道,内存可以扩展,但IO处理对效率的影响会更大;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库