ez7268 2022-08-02 19:49 采纳率: 0%
浏览 4363
已结题

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条回答 默认 最新

  • 郭老师的小迷弟雅思莫了 Java领域新星创作者 2022-08-03 10:56
    关注
    获得25.00元问题酬金

    1.你方法加上了声明事务 @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
    如果save失败时,事务会自动回滚。
    2.我没有在你的方法里看到你有捕获错误,也没有log记录日志,所以日志里异常是没有写入的。
    3.你可以在部署的nohup.out文件中找寻错误日志。
    4.你这种几千条成功,几百条失败,有可能是数据库某个字段长度不够等导致的。你可以查查。
    5.也有可能网络原因(概率小)
    6.数据库是否使用的是长连接?是否超过最大连接数?
    7.建议你替换成这个: @Transactional(rollbackFor = DataAccessException.class)

    /**
     * 数据库自定义异常
     *
     * @author yuanpeng
     * @date 2018/12/28 0028 下午14:05
     */
    public class DataAccessException extends RuntimeException {
    
        public DataAccessException() {
            super();
        }
    
        public DataAccessException(String message) {
            super(message);
        }
    
        public DataAccessException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public DataAccessException(Throwable cause) {
            super(cause);
        }
    }
    
    
    评论 编辑记录

报告相同问题?

问题事件

  • 系统已结题 8月10日
  • 修改了问题 8月4日
  • 修改了问题 8月4日
  • 修改了问题 8月4日
  • 展开全部

悬赏问题

  • ¥15 求.net core 几款免费的pdf编辑器
  • ¥20 SQL server表计算问题
  • ¥15 C# P/Invoke的效率问题
  • ¥20 thinkphp适配人大金仓问题
  • ¥20 Oracle替换.dbf文件后无法连接,如何解决?(相关搜索:数据库|死循环)
  • ¥15 数据库数据成问号了,前台查询正常,数据库查询是?号
  • ¥15 算法使用了tf-idf,用手肘图确定k值确定不了,第四轮廓系数又太小才有0.006088746097507285,如何解决?(相关搜索:数据处理)
  • ¥15 彩灯控制电路,会的加我QQ1482956179
  • ¥200 相机拍直接转存到电脑上 立拍立穿无线局域网传
  • ¥15 (关键词-电路设计)