开发技巧2:学会通过while、do-while或for循环进行业务分批处理


  这个专栏之前定下的计划是每周会发布一篇,但现在距离上一篇文章的发布已经过去了3个多月的时间了。希望自己从这周开始接下来坚持每周至少输出一篇文章!欢迎各位同学朋友们积极监督!

  好,废话少说!现在开始进入正题。这次教给大家的开发技巧就是学会使用while或do-while这两种循环结构进行分页查询,这个分页查询的方式主要包括了查询数据库和调用接口这两种方式。可能这么说大家未必明白,没关系,接下来听我详细讲解。

  比如有个业务需求是这样的:将系统A中所有用户的信息上报到B系统中进行数据分析。假设系统A的用户表为t_user(主键字段:id),系统B提供了一个名为analyzeUserInfo(List users)这么一个接口(UserInfo类的属性信息跟t_user表中的字段一一对应)。那么你是如何做的呢?有些同学可能想到了一个直接粗暴的方法也就是把t_user表中的数据全部查出来,然后在代码中进行处理。这种方法也不是不可以,但是如果t_user这张表的数据量是千万级别的话,你依然选择把全部数据放在内存中处理吗?答案当然不是这样。因为这么多的数据量放在内存中操作的话大概率会引发OOM异常的。所以接下来我就讲解下我是怎么处理的。
  1. 分批处理。 首先我们可以针对t_user进行分页,不建议使用limit ?,?这种方式进行分页。为什么呢?因为阿里巴巴开发手册中已经说得很明白:

MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。

所以得换种分页的方式,我们可以这样进行分页:select * from t_user where id > #{maxId,jdbcType=BIGINT} order by id asc limit #{size,jdbcType=BIGINT} 。通过order by id从小到大进行分页查询,首先maxId的值初始化为0,然后第二次循环的maxid = 0 + size, 接着以此类推,直到查询出的表t_user信息为空或者查询记录数小于每页的大小size时就跳出循环。

2.如果以上还是看不懂,没关系!接下来我直接上个伪代码,大家可以细品下,如下所示:
第一种方法:使用while循环

// 处理用户信息
public void handleUserInfo() {
	// 偏移量初始化为0
	int maxId = 0;
	// 每页大小初始化为500
	int size = 500;
	
	while(true) {
	    // 里面执行的就是select * from t_user where id > #{maxId,jdbcType=BIGINT} order by id asc limit #{size,jdbcType=BIGINT}这个sql
		List<User> userList = userService.getUsers(maxId, size);
		// (1)判断userList是否为空,如果为空则跳出循环
		if(userList == null || userList .isEmpty()) {
			break;
		}
		// 业务逻辑处理 - 数据分析
		analyzeUserInfo(userList)// (2)判断userList的大小是否小于每页大小,如果小于则跳出循环
		if(userList.size() < size) {
			break;	
		}
		// 更新偏移量:因为sql中已经有order by id asc,所以只需取列表中最后一项的id即可
	    maxId = userList.get(userList.size() - 1).getId();
}

第二种方法:使用do-while循环

// 处理用户信息
public void handleUserInfo() {
	// 偏移量初始化为0
	int maxId = 0;
	// 每页大小初始化为500
	int size = 500;
	List<User> userList;
	
	do {
	    // 里面执行的就是select * from t_user where id > #{maxId,jdbcType=BIGINT} order by id asc limit #{size,jdbcType=BIGINT}这个sql
		userList = userService.getUsers(maxId, size);
		if(userList != null && !userList.isEmpty()) {
			// 业务逻辑处理 - 数据分析
			analyzeUserInfo(userList)// 更新偏移量:因为sql中已经有order by id asc,所以只需取列表中最后一项的id即可
	   	    maxId = userList.get(userList.size() - 1).getId();
		}	
    } while(userList != null && !userList.isEmpty() && userList.size() == size);
}

  因为代码中已经写得比较清楚啦,所以我就不做过多的文字解释了。相信认真读过上面代码的同学已经知道我想表达的的意思啦。当然如果再细致点的话,我们可以对analyzeUserInfo(userList)做个try-catch处理,或者在analyzeUserInfo方法内部做处理也没问题,这样做的目的就是避免一次循环的报错导致的整个循环的退出。

  今天下午跟一位同事讨论时他指出我这种while或do-while的写法可能会有死循环的风险,但我觉得只要我们控制好偏移量maxId和每页大小size,就不会出现死循环的问题。同时他还指出了如下的写法,也就是先计算出userList的总页数,然后使用for循环进行处理,伪代码如下所示:

第三种方法:使用for循环

  // 获取总页数
  public static int getPage(Integer size, int step) {
        int page = size / step;
        page += size % step > 0 ? 1 : 0;
        return page;
   }
   
// 处理用户信息
public void handleUserInfo() {
	  // 偏移量初始化为0
	  int maxId = 0;
	  // 每页大小初始化为500
	  int size = 500;
	  Integer totalSize = userService.count();
	  Integer totalPage = getPage(totalSize, 500);
	  for(int i = 0; i < totalPage; i++) {
		    // 里面执行的就是select * from t_user where id > #{maxId,jdbcType=BIGINT} order by id asc limit #{size,jdbcType=BIGINT}这个sql
			List<User> userList = userService.getUsers(maxId, size);
			// 业务逻辑处理 - 数据分析
			analyzeUserInfo(userList)// 更新偏移量:因为sql中已经有order by id asc,所以只需取列表中最后一项的id即可
		    maxId = userList.get(userList.size() - 1).getId();
	   }
 }

  本人感觉这个方法也是不错的!哈哈,看来多跟别人讨论还是对自己非常有帮助的!以上的三个方法中,本人推荐使用第一种方法:使用while循环,因为个人感觉这种方法会更直观并更好理解点。

  好,完毕!

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Melo_FengZhi

您的鼓励对我就是巨大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值