【Real World Case】异常捕获失败导致事务未提交问题


1、问题现象

从用户层面看,问题时段多个功能出现功能卡住、”Lock wait timeout exceeded; try restarting transaction”等事务等待超时报错。

从数据库层面看,跟踪数据库会话,发现存在较多阻塞,阻塞源会话ID固定,阻塞源会话为非活动状态,会话相关SQL为select操作,且阻塞源会话SQL一直在变化。通过数据库会话可以得知,存在DB阻塞,且阻塞源唯一确定,阻塞源会话SQL已执行完,可以排除事务内的个别SQL慢导致事务未提交的情况,该问题应该是由于产品层面出现事务未提交或某种原因导致应用端hang住有关。

从应用层面看,CPU、内存等资源占用很低,不存在资源瓶颈;分析应用线程dump,活动线程均为等待数据库反回状态,未发现明显异常,基本可以排除应用逻辑Hang导致事务粒度大的问题。

         检查控制台日志,存在如下事务未提交的日志:

        Line 13473: 2021-12-30 14:33:20,042 iZ37ac50e7eeg3Z ERROR [8636.c2c45bc.19284.6661] [CM] io.iec.edp.caf.context.filter.CAFContextFilter [http-nio-5200-exec-54] 请求结束时存在未提交的事务,当前请求:/api /产品Url/···

        Line 14038: 2021-12-30 14:33:24,382 iZ37ac50e7eeg3Z ERROR [8636.c2c45bc.19284.6661] [CM] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[CXFServlet] [http-nio-5200-exec-54] Servlet.service() for servlet [CXFServlet] in context with path [] threw exception [系统检测到存在未提交的事务,请联系管理员检查!] with root cause

         Line 14039: javax.servlet.ServletException: 系统检测到存在未提交的事务,请联系管理员检查!

 2、问题原因

根据控制台报错日志,在记录事务未提交日志前,相同线程出现java.lang.NoClassDefFoundError异常,该异常类型为Error的派生类,检查产品代码异常处理中仅捕获了Exception,Exception异常捕获规则无法捕获Error类的异常,导致异常处理失败。

 //问题代码
try {
// ***
Throw java.lang.NoClassDefFoundError
// ***
} catch (RuntimeException e) {
    BizLogHelper.error("RuntimeException Info", e);
    platformTransactionManager.rollback(status);
    // ***
} catch (Exception e) {// 此处只能捕获exception,无法捕获ERROR
    BizLogHelper.error("Exception Info", e); //先处理事务状态,再处理其他逻辑
    platformTransactionManager.rollback(status);
    // ***
}

同时检查代码过程还发现异常处理不够严谨,建议在catch异常处理代码块中先处理事务状态,再进行其他异常逻辑处理。

 根据前面分析,针对“异常捕获类型不当”和“异常处理代码块不严谨”的问题代码做如下优化,优化后提供项目部署后问题解决。

  //优化方案
try {
// ***
// Fix java.lang.NoClassDefFoundError
// ***
} catch (RuntimeException e) {
    platformTransactionManager.rollback(status);
    BizLogHelper.error("RuntimeException Info", e);
    // ***
} catch (Throwable e) {// 捕获Throwable类型异常
    platformTransactionManager.rollback(status); /先处理事务状态
    BizLogHelper.error("Exception Info", e);
   // ***
}

3、总结沉淀

Throwable 类是 Java 语言中所有错误或异常的超类。Error 和 Exception是Throwable的两个子类的实例,通常用于指示发生了异常情况。在程序运行过程中出现异常时,只有抛出的异常类型为catch子句中的参数类型或其子类之一才可以被捕获。部分异常示例如下,具体可以参考Java API文档https://tool.oschina.net/apidocs/apidoc?api=jdk-zh 。