美文网首页
数据库更新防并发错误

数据库更新防并发错误

作者: 翔哥不哭 | 来源:发表于2019-01-18 15:58 被阅读0次

    1. 更新的操作存在的问题

    • 1.1 infomation表每一条诉求记录都是有状态,但是更新记录状态之前需要查询该记录状态,但是一般事务设置的隔离级别,对查询是不会加锁,所以查询出来的状态可能不是最新的,最后更新达不到预想的结果
        @Override
        public Information updateInformationStateCodeForNotice(Long informationId, Long orgId, String departmentNo, Integer stateCode) {
            Information information = getInformationById(informationId);
            // 这里会进行状态逻辑判断
            if(InformationStateCode.INFORMATION_STATECODE_PROCESS.equals(stateCode)
                    && stateCode.equals(information.getStateCode())) {
                return information;
            }
            // 这里会进行状态逻辑判断
            if (information.getStateCode() != null && information.getStateCode() > stateCode) {
                throw new BusinessValidationException("无法执行该操作");
            }
            try {
                information.setStateCode(stateCode);
                if (InformationStateCode.INFORMATION_STATECODE_WAIT.equals(stateCode)) {
                    information.setCurrentOrgId(orgId);
                    information.setCurrentDepartmentNo(departmentNo);
                }
                //这里更新的时候就有可能状态已经变化,如果此时再更新状态就会错乱
                informationMapper.updateInformationStateCodeById(information);
                return information;
            } catch (ServiceValidationException e) {
                throw new ServiceValidationException("更新诉求状态失败!", e);
            }
        }
    

    2. 并发修改方案

    A方案 update锁

    单条记录查询的时候加上 for update锁,待当前事务提交即释放锁,其他线程才获取锁,这样可以保证查询的结果在当前事务中有效,但是此方法有一定风险,可能会永远锁住某一条记录,也有可能造成死锁,所以不是最优选择。

    如:
    //普通查询方法
    InformationService.getInformationById()
    //加X锁查询方法
    InformationService.getInformationByIdForUpdate()

    x锁sql
    如:
    getInformationByIdForUpdate:
    Select * from informations
    WHERE id = #{id} for update

    B方案 where带上符合条件的状态

    更新的时候where带上符合条件的状态,根据返回结果,是否更新成功,若为更新成功可根据业务是否需要回滚,都可以自行控制,但是此方法只适合查询判断逻辑简单的业务,如需要判断多种条件而且不仅判断当前要更新的记录,还要判断关联数据的逻辑,此方法还是不能保证数据的一致性。

    如:通过判断更新返回值,如果没有更新成功,则抛异常回滚。

    • B1代码 :事务内的逻辑,判断要更新的车辆那条记录状态是否能更新
     @Override
        public boolean addApplyCarKeepRecord(CarKeepRecordVO carKeepRecordVO) {
            if (carKeepRecordVO == null) {
                throw new BusinessValidationException("参数不能为空");
            }
            CarKeepRecord carKeepRecord = carKeepRecordVO.getCarKeepRecord();
            if (carKeepRecord == null) {
                throw new BusinessValidationException("参数不能为空");
            }
            CarInfo carInfo = validateCarInfoExsit(carKeepRecord.getCarId());
            try {
                carKeepRecord.setCarId(carInfo.getId());
                carKeepRecord.setCarName(carInfo.getCarName());
                carKeepRecord.setStartDate(new Date());
                addCarKeepRecord(carKeepRecord);
                int updateCarStatusByStatusEnum = carInfoService.updateCarStatusByStatusEnum(carInfo.getId(), EnumCarStatusUpdate.published_to_keeping);
                if (updateCarStatusByStatusEnum == 0) {
                    throw new BusinessValidationException(EnumCarStatusUpdate.published_to_keeping.getErrorText());
                }
                return true;
            } catch (BusinessValidationException e) {
                throw new ServiceValidationException(e.getMessage(), e);
            } catch (Exception e) {
                throw new ServiceValidationException("提交维护异常", e);
            }
        }
    
    • b2代码 更新状态的统一方法,所以更新状态的业务,都调用此方法
    @Override
        public int updateCarStatusByStatusEnum(Long carId, EnumCarStatusUpdate carStatusEnum) {
            if (carId == null) {
                throw new BusinessValidationException("参数为空异常");
            }
            CarInfo orderInfo = getCarInfoById(carId);
            if (orderInfo == null) {
                throw new BusinessValidationException("车辆不存在异常");
            }
            if (carStatusEnum == null) {
                throw new BusinessValidationException("车辆更新参数出现异常");
            }
            try {
                CarInfoVO carInfoVO = new CarInfoVO();
                carInfoVO.setCarId(carId);
                carInfoVO.setFromStatus(carStatusEnum.getFrom());
                carInfoVO.setToStatus(carStatusEnum.getTo());
                return carInfoMapper.updateCarStatus(carInfoVO);
            } catch (Exception e) {
                throw new ServiceValidationException("车辆状态更新出错", e);
            }
        }
    
    • b3代码 更新状态的sql
            UPDATE
                uc_sys_car_info
            SET
                update_date = now(),
                car_status  = #{toStatus}
            WHERE id = #{carId} AND car_status = #{fromStatus} AND is_deleted = 0
    
    C方案 redis 做分布式锁

    大部分业务流量并发,redis的毫秒级别算安全,所以应该统一采用redis来做分布式锁,这样也不用锁表,还能保证数据一致性

    • 以下是伪代码,仅展示思路

    如:
    //普通查询诉求方法
    Information getInformationById(Long id)
    //加redis锁查询诉求方法
    Information getInformationByIdForUpdateWithRedis(Long id)

    getInformationByIdForUpdateWithRedis方法实现 如:

    //加redis锁查询诉求方法
    Information getInformationByIdForUpdateWithRedis(Long id){
        boolean lock = false;
        Int getlockCount = 0
        do {
              If(getlockCount !=0 && getlockCount <6){
                Sleep(1000)//睡一秒
              }
              If(getlockCount >=6){
                //尝试获取锁6次都没有获取到,很大可能该id所属的诉求被锁住了
                    //可以抛异常,也可以把key抛出,由调用方去处理
              }
            lock = RedisUtils.getInfomationLock(id)
          }while(!lock);
          Return getInformationById(id);
    }
    
        RedisUtils.class的getInfomationLock方法
        //获取诉求记录锁
        Boolean getInfomationLock(Long id){
            String key = “infomationLock:” +id;
            Int reslut = redisClient.setnx(key,1)
            If(reslut == 1){
            Return true
            }
            return false;
        }
    
        //释放诉求记录锁
        Void unInfomationLock(Long id){
            String key = “infomationLock:” +id;
            redisClient.del(key,1)
        }
    

    相关文章

      网友评论

          本文标题:数据库更新防并发错误

          本文链接:https://www.haomeiwen.com/subject/jeundqtx.html