ez7268
2022-08-02 19:49
采纳率: 0%
浏览 3.8k

springboot+mybatis,声明式事务偶尔不提交的问题,最高10倍悬赏

更新记录

  1. 2022-08-03 10:32:修改了代码片段getErrorOrder上面的注释
  2. 2022-08-03 13:52:增加数据库连接池配置
  3. 2022-08-04 09:27:修改orderService.save(prevSubPlanErrorOrder)为orderService.save(errorOrder)
  4. 2022-08-05 09:52:OrderEntity errorOrder = getErrorOrder(key, user) 上方增加重要注释!!!

问题描述

主要的问题如标题所述,就是发现在进行事务操作时,比如save、update时,进行5000次操作,可能会有几百1000次没有提交,也没报错的情况,而出现这种情况时,我们发现在数据库里面会存在,十几个sleep的长事务(持续几千秒)。

这个问题是在生产上出现的,在本地运行,或开发环境运行时无法复现。另外这套代码,我们独立部署了两套,两边的配置都是一模一样的,都是阿里云的ecs+rds mysql,并发也差不多,而现在只有一边出了问题,另一边正常。

环境及依赖描述

  • 服务器:阿里云ECS 4核16G
  • 数据库:阿里云 RDS Mysql 8.0(innoDb) 4核16G
  • java版本:java8
  • 项目框架:springboot+mybatis

代码片段

  • 其实出问题的代码不止只一点,只是因为运行中,save订单的场景是最多的,所以就贴这里的代码,代码我经过了简化,只保留了关键的逻辑部分。
    这个方法只会从controller进入,下面对代码逻辑进行描述,代码逻辑很简单:
    1、查询用户信息
    2、根据用户信息去redis查询之前的错误订单,如果查询到错误订单,就从第三方接口查询错误订单的状态,并保存,然后方法结束
    3、如果没查询到之前的错误订单,就生成新订单,并提交到三方接口

      @Override
      @SneakyThrows
      @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
      public void submitOrder(OrderDTO dto) {
          
    
          //查询签约用户信息,这里只有查询操作
          UserEntity user = userService.getUser(dto.getUserBizid());
          if (user == null) {
              throw new BizException(Msg.MUST_BIND_CHANNEL);
          }
    
          // 从redis中取出之前提交失败的订单,重新查询该订单的状态,如果成功则不需要重新发起订单,直接保存该订单即可,如果失败才需要走后面的流程
          
          //生成该订单的子计划的异常订单redis key,该方法只有字符串操作
          String key = redisKeys.generateErrorOrderKey();
    
          //getErrorOrder这个方法是本service中的方法,该方法声明为private,没有事务注解,该方法中会调用其他service的方法
          //其他service的方法中有可能会存在编程事务,这些编程事务的事务传播为PROPAGATION_REQUIRES_NEW,这些编程事务在其方法退出前都保证commit或rollback了的
          OrderEntity errorOrder = getErrorOrder(key, user);
          if (errorOrder != null) {
              orderService.save(errorOrder);
              //清除redis中的异常订单记录
              redisComponent.delete(key);
              return;
          }
    
          //未查询到之前的失败订单,组装新的order(此处我省略了一些set操作,为了方便大家观看)
          OrderEntity orderEntity = new OrderEntity();
          orderEntity.setDefaultClientRate(dto.getDefaultClientRate());
          orderEntity.setDefaultClientTips(dto.getDefaultClientTips());
          orderEntity.setRatePrice(dto.getRatePrice());
          orderEntity.setTipsPrice(dto.getTipsPrice());
          orderEntity.setDebitCardBankBizid(user.getBankBizid());
          orderEntity.setLocation(dto.getLocation());
          orderEntity.setBizid(idWorkerComponent.nextStringId());
          //这里是调用的mapper自带的save方法  
          orderService.save(orderEntity);
    
          //获取调用第三方接口的service
          PayChannelService payChannelService = PayChannelFactory.getPayChannel();
          //调用三方接口,trade方法里面会有save、select、和http的相关操作
          ClientResultDTO resultDTO = payChannelService.trade();
    
          if (!resultDTO.isSuccess()) {
              //如果三方接口调用失败,则抛出异常,由于方法头加了事务注解,所以理论上应该回滚
              throw new BizException(Msg.BALANCE_REPAY_SUBMIT_ORDER_ERROR.getCode(), resultDTO.getResultMsg());
          }
      }
    

现在的问题是,这个方法没报异常,但是有时候数据并没有save到库中

当出现问题的时候,我们在阿里云rds的性能监控界面,会看到很多挂起的会话,如图:

img

数据库连接池配置

# -------------------druid 配置-------------------
# 初始化时建立物理连接的个数
spring.datasource.druid.initial-size = 50
# 最大连接池数量
spring.datasource.druid.max-active = 5000
# 最小连接池数量
spring.datasource.druid.min-idle = 50
# 获取连接时最大等待时间,单位毫秒
spring.datasource.druid.max-wait = 10000
# 连接保活
spring.datasource.druid.keep-alive=true
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis = 60000
# 连接保持空闲而不被驱逐的最小时间
spring.datasource.druid.min-evictable-idle-time-millis = 300000
# 用来检测连接是否有效的sql,要求是一个查询语句
spring.datasource.druid.validation-query = SELECT 1 FROM DUAL
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.test-while-idle = true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-borrow = false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-return = false
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计
spring.datasource.druid.filters = stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connection-properties = druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 合并多个DruidDataSource的监控数据
spring.datasource.druid.use-global-data-source-stat = true
spring.datasource.druid.stat-view-servlet.enabled = true
spring.datasource.druid.web-stat-filter.enabled = true

我现在就是一脸懵逼,因为这个问题是偶发的,就像开头说,save 几千次,可能会出现几百次没入库的情况,没入库的原因应该就是事务没提交,但是不知道为什么没提交。希望有朋友能提供一下排查思路,或手把手指导一下,可以VX,由于CSDN上面的最高只能给到500,不过如果能定位和解决问题的话,我可以给到10倍。

51条回答 默认 最新

相关推荐 更多相似问题