多个定时任务更新同一个表,怎么确保数据不重复?

需求:
有一个表叫目标表,这里存了对应每个员工个日、周和月目标。这个表里面的字段大致分为4类,每一类记录不同方面的业绩,例如a记录订单,b记录销售,然后每一类有一个目标数量和真实数量。现在需要每天晚上12点之后都重新计算一下所有员工的昨日目标和最近的周、月目标完成的真实数量,表结构大致是下面这个样子。

clipboard.png

出现的问题:
现在的实现方式是写了4个定时任务,比如methodA、methodB、methodC和methodD来分别计算不同的目标,我是先获得了所有的员工的集合,然后放到for循环里面,单独查出每个员工的对应的那条目标数据,重点在于有的员工可能没有给自己创建目标,这个时候我就要给他创建一条目标数据,只填写真实数量,目标数量就空着,如果他自己已经创建了我就直接更新这条数据里对应的数量字段,代码逻辑大概是这样的。

public void methodA(){
    //查询所有员工的id
    List<Integer> userIds = userService.findList(queryVo);
    for(Integer userId:userIds){
    //查询每个老师的目标数据
    GoalsVo goalsVo = new GoalsVo();
    goalsVo.setUserId(userId);
    if (goalsVo!=null){
        //更新
    }else{
        //新增一条数据
    }
}

问题就在于因为因为是分成4个方法去更新同一个员工的不同的数据,如果当methodA没有查到目标数据,走到了新增数据的逻辑里面时但是还没执行插入的时候,刚好methodB也查到这个员工没有目标数据,也走到了新增数据的逻辑里面,这样的话就会造成一个员工有两条目标记录数据。

解决思路:
现在想到了一些思路但是感觉还是有问题。
1.再写一个定时任务,专门检查有没有创建目标数据,但是数据多了话,也可能造成那个上述结果
2.将每个定时任务之间的时间拉长一点,错开,治标不治本。
3.设置索引,避免一个员工同一天有多个日目标数据,但是这样的话后面执行的方法里面的数据就没办法更新到前面先创建的数据里,造成数据丢失

请问有什么好的解决方法吗?

=======================================
非常感谢大家提供的思路,验证了一下,这些思路都是可以实现的,试过HamilFei这位朋友加锁的思路是可以解决问题的。
2018.10.14

阅读 9.3k
5 个回答

在查询数据库是否存在该userId前加锁吧。

public void methodA() {
        //查询所有员工的id
        List<Integer> userIds = userService.findList(queryVo);
        for (Integer userId : userIds) {
            synchronized (SingleByUserId.getById(userId)){
                //根据userId去数据库查询是否存在
                GoalsVo goalsVo = new GoalsVo();
                goalsVo.setUserId(userId);
                if (goalsVo.isNotEmpty()) {
                    //更新
                } else {
                    //新增一条数据
                }
            }
        }
    }
//非线程安全实现,需要修改
class SingleByUserId {

    static Map<Integer, Object> map = new HashMap<>();

    static Object getById(Integer userId) {
        if (map.get(userId) != null) {
            return map.get(userId);
        } else {
            Object object = new Object();
            map.put(userId, object);
            return object;
        }
    }
}

设置锁定标志
哪个程序想更新先查询有木有锁定标志,没有的话,就设置成锁定,开始更新
更新完毕,设置解除锁定

设置触发器 去重,或许可以

提供几个思路:
一、在所有的定时任务之前,做用户统计记录的初始化工作,这样在统计的定时任务中,就不用判断是否存在用户记录,在每个任务中更新自己做统计的字段就可以了。

二、数据后处理,把由重复的数据做一次汇总就行了,然后删除重复的数据行。

三、统计表加主键约束或唯一索引,保证不能插入重复数据。然后程序逻辑中,对插入数据做异常处理,如果是数据重复的异常,就重新执行一次判断数据是否重复及后续处理逻辑。

根据实际情况,判断新增的情况占比。
如果每日的数量不大,将新增线程提取为独立线程,此线程中进行新增的判断,比如使用日志记录每日insert的ID,防止重复插入,当其他现场需要新增数据时,与此线程交互。类似的还可以使用缓存,队列等来处理。
如果每日新增数量较大,4个线程间分区,比如线程一循环1-100个,线程二循环1-200个。
另外也可以使用数据库的主键策略或者约束等,保证只有第一次插入成功,其后的违反约束,使用try catch丢弃

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题