1. 问题
- 有一个定时任务,需要批量插入数据库,一次性插入200条数据没有问题,一次性插入500条,就没有反应了
- 开发环境本地测试没有问题,测试和生产都有问题。
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. 结合源码分析异常堆栈
-
异常堆栈日志显示栈溢出。
-
结合java.lang.StackOverflowError的原因,主要可能是单个线程栈异常,调用栈深度太深,可能是递归调用
-
查看异常堆栈日志,不断再循环如下调用,表明有递归调用。
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)
-
查看源代码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. 回归业务代码,改造问题
- 业务代码中发现,mybatis批量插入,是通过xml文件配置foreach,通过union拼接,解析sql的时候引起了递归调用
- 我们可以改造为打开mybaits batch批量操作方式,这样sql只需要解析一次了
5. 问题总结
- 每个jvm线程都有一个线程栈,用于存放当前线程的调用方法,局部变量,返回地址等。如果该线程的线程栈空间满了,就会抛出java.lang.StackOverflowError。
- 我们可以利用-Xss256K jvm参数来设置单个线程栈大小