mybatis foreach批量操作栈溢出java.lang.StackOverflowError

1. 问题
  1. 有一个定时任务,需要批量插入数据库,一次性插入200条数据没有问题,一次性插入500条,就没有反应了
  2. 开发环境本地测试没有问题,测试和生产都有问题。
2. 在Skywalking中查找到错误日志

异常堆栈如下:

stack:
java.lang.StackOverflowError
at com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor.visit(OracleOutputVisitor.java:621)
at com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectTableReference.accept0(OracleSelectTableReference.java:80)
at com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectTableReference.accept0(OracleSelectTableReference.java:76)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor.visit(OracleOutputVisitor.java:519)
at com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectQueryBlock.accept0(OracleSelectQueryBlock.java:98)
at com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectQueryBlock.accept0(OracleSelectQueryBlock.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2962)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
3. 结合源码分析异常堆栈
  1. 异常堆栈日志显示栈溢出。

  2. 结合java.lang.StackOverflowError的原因,主要可能是单个线程栈异常,调用栈深度太深,可能是递归调用

  3. 查看异常堆栈日志,不断再循环如下调用,表明有递归调用。

    at com.alibaba.druid.sql.visitor.SQLASTOutputVisitor.visit(SQLASTOutputVisitor.java:2990)
    at com.alibaba.druid.sql.ast.statement.SQLUnionQuery.accept0(SQLUnionQuery.java:90)
    at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
    
  4. 查看源代码SQLASTOutputVisitor.visit;代码如下:

    @Override
    public boolean visit(SQLUnionQuery x) {
        SQLUnionOperator operator = x.getOperator();
        SQLSelectQuery left = x.getLeft();
        SQLSelectQuery right = x.getRight();
        ...
        if (needParen) {
        print('(');
        right.accept(this);
        print(')');
        } else {
            right.accept(this);
        }
        ...
    }
    

    以上代码大致意思是:根据union分隔符来分隔sql,解析sql,union右侧的继续解析,然后引起递归调用

4. 回归业务代码,改造问题
  1. 业务代码中发现,mybatis批量插入,是通过xml文件配置foreach,通过union拼接,解析sql的时候引起了递归调用
  2. 我们可以改造为打开mybaits batch批量操作方式,这样sql只需要解析一次了
5. 问题总结
  1. 每个jvm线程都有一个线程栈,用于存放当前线程的调用方法,局部变量,返回地址等。如果该线程的线程栈空间满了,就会抛出java.lang.StackOverflowError。
  2. 我们可以利用-Xss256K jvm参数来设置单个线程栈大小
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值