基于数据的时间范围查询,给符合条件的用户推送积分即将到期的提醒。
初期用户量小使用最普通简单的分页查询扫描数据处理数据没问题。随着用户量的上升表数据已经上千万,每天扫描处理的数量也超百万,limit分页出现了慢sql,任务执行时间也达不到预期了。
上述方案出现瓶颈后考虑放弃limit方案,使用游标的方式进行全量数据的获取,这样一来SQL执行快任务执行也快。
使用游标查询时,,必须在jdbc url上设置连接属性参数useCursorFetch=true
在设置了useCursorFetch=true后,需要在SQL中指定fetchSize,即一次获取的数据量。
如果不设置fetchSize参数,则执行时仍然是全量返回,可能会出现OOM。
mapper接口方法返回值声明为Cursor类型,下面是SQL和Mapper的示例。
Cursor<Long> selectExpireCouponMember(@Param("endTime") String endTime);
<select id="selectExpireCouponMember" resultType="java.lang.Long" fetchSize="5000">
select
distinct member_id
from t_dj_coupon
where
end_time > end_time < #{endStartTime} and end_time < #{endTime}
</select>
?下面是基于上面的SQL做大量数据查询后写入文件的代码示例。
String fileName = DateFormatUtils.format(System.currentTimeMillis(), DateUtils.PATTERN_YYYY_MM_DD) + "_status_push_member.txt";
File file = new File(fileName);
file.createNewFile();
fileWriter = new FileWriter(file);
bufferedWriter = new BufferedWriter(fileWriter);
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
DjCouponMapper mapper = sqlSession.getMapper(DjCouponMapper.class);
try (Cursor<Long> cursor = mapper.selectStartCouponMember(startDate, startDateEnd, BrandContextHolder.getBrandMdCode())) {
Iterator<Long> iterator = cursor.iterator();
Set<Long> couponMemberSet = new HashSet<>(pageSize.intValue());
while (iterator.hasNext()) {
couponMemberSet.add(iterator.next());
writeNum++;
if (couponMemberSet.size() >= pageSize) {
bufferedWriter.write(couponMemberSet.toString());
bufferedWriter.newLine();
bufferedWriter.flush();
writeLine++;
couponMemberSet.clear();
}
}
if (CollectionUtils.isNotEmpty(couponMemberSet)) {
bufferedWriter.write(couponMemberSet.toString());
bufferedWriter.newLine();
bufferedWriter.flush();
writeLine++;
}
}
}
ResultSetImpl 是mybatis中实现游标查询结果解析的类。