背景
在实际开发过程中,不得已的情况下需要去更新数据库某个或多个字段,假如需要更新的字段目标值是一个固定值,那么用update语句就可以实现,数据量比较大的情况下,一般也不会有问题。如果说需要更新的字段目标值是需要依赖同一条记录其他字段做一定计算的结果,这时可以选择使用sql,也可使用代码去实现。再如果说这个字段目标值是需要调用接口或其他不能直接计算出结果时(如某个加密值),这时就可以选择代码去实现,需要更新的数据量比较大时,则需要采用多线程的方式去更新。在实际实现的过程中遇到了一个更新不成功的逻辑问题,虽然很快就发现了问题所在,还是秉着好记性不如烂笔头的精神记录下。
多线程更新实现方式
采用分页获取指定行数数据,如指定60行,分别交给不同线程去更新这60行的数据,更新的主键为每条数据的id,线程池ThreadPoolExecutor核心参数按实际情况指定。
多线程更新出现问题
在更新过程中会出现的问题:某些记录未成功更新,原因在后面进行说明,先放出部分测试代码:
public String updateOrdersJob(HttpServletRequest request){
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
10,30,100, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy());
try{
logger.info("充值订单批量更新");
OrdersExample example = new OrdersExample();
OrdersExample.Criteria criteria = example.createCriteria();
criteria.andServerEqualTo("01");
criteria.andPhoneTypeEqualTo("1");
int sumCount=ordersMapper.countByExample(example);
int num=60;
int times=sumCount/num;
if(sumCount%num!=0){
++times;
}
logger.info("需要更新总条数:{},分页数:{},页大小:{}",sumCount,times,num);
for(int i=0;i<times;i++){
threadPool.execute(new activeJob(i+1,num));
}
}catch (Exception ex){
logger.error("充值订单更新异常:"+ex.getMessage(),ex);
return "false";
}finally {
threadPool.shutdown();
}
return "Success";
}
class activeJob implements Runnable {
private int index;
private int num;
public activeJob(int index,int num){
this.index=index;
this.num=num;
}
@Override
public void run() {
logger.info(Thread.currentThread().getName() + "线程开始调用,更新范围:index:{},num:{}",index,num);
try{
OrdersExample example = new OrdersExample();
OrdersExample.Criteria criteria = example.createCriteria();
criteria.andServerEqualTo("01");
criteria.andPhoneTypeEqualTo("1");
List<Orders> ordersList = ordersMapper.selectByExample(example, new Page<>(index, num));
Iterator<Orders> iterator = ordersList.iterator();
while (iterator.hasNext()) {
Orders order = iterator.next();
order.setPhoneType("0");
ordersMapper.updatePhoneTypeByPrimaryKey(order);
}
logger.info(Thread.currentThread().getName() + "线程调用完成,更新范围:index:{},num:{},更新条数:{}",index,num,ordersList.size());
}catch (Exception e){
logger.error(Thread.currentThread().getName() + "线程调用异常,更新范围:index:{},num:{}", e.getMessage(), e);
}
}
}
执行情况:
从日志能看出,线程11和12未获取到响应记录,从而导致这部分记录未能更新成功;那么问题的核心就是为什么这部分的分页数据未能获取到。
原因
相信看了源码的同学大概看出了问题的问题的所在,即多线程在执行的过程中,分页查询的条件中有一个字段PhoneType既是查询条件又是需要更新的字段,也就是说部分线程在提交更改后,会导致此时分页数据发生变化,就会导致分页查询不到数据的情况。
解决方式
分页查询条件,可以根据实际情况去掉或用其他字段当做查询条件,保证获取的数据正确。
总结
在用多线程更新时,一定要根据实际情况,避开逻辑漏洞