You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从GlassFish迁移至TomEE时Oracle数据库性能退化与连接池问题

批量插入性能衰减排查问题

问题背景

将老旧项目从Java 7迁移至Java 8,同时从GlassFish迁移至TomEE,使用Oracle 19c数据库、JDBC驱动版本8。保存数据时出现性能衰减:单条/批量保存初始耗时约60ms,但每次调用statement.executeBatch()或statement.executeQuery()的耗时逐渐增加,最终可达5分钟,进而引发连接池错误,该问题在GlassFish上未出现。

核心插入代码

Connection con = null;
PreparedStatement statement = null;
CallableStatement call = null;
try {
    con = HermesDS.getConnection();

    String extendedSql = "INSERT INTO hrm_wi_influence_for_ins(doc_type, doc_id, influence, cln_segment_type," +
            "is_user_input, pw_from_date, pw_to_date, planned_stop, inform_client," +
            " customer_number, service_type, address, customer_name, customer_segment, customer_sub_segment," +
            "SLA_CODE) " +
            "SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM DUAL";

    statement = con.prepareStatement(extendedSql);
    int j = 0;
    final int maxBatchSize = 30;
    con.setAutoCommit(false);
    long time = System.currentTimeMillis();

    for (Map.Entry<String, InfluenceData> entry : influences.entrySet()) {
        String influence = entry.getKey().trim();
        InfluenceData influenceData = entry.getValue();

        statement.setString(1, docType);
        statement.setBigDecimal(2, docId);
        statement.setString(3, influence);
        statement.setString(4, influenceData.getSegment());
        statement.setBigDecimal(5,
                (influenceData.getIsUserInput() != null && influenceData.getIsUserInput())
                        ? BigDecimal.ONE
                        : BigDecimal.ZERO);

        statement.setString(6, influenceData.getPlanStart());
        statement.setString(7, influenceData.getPlanEnd());
        statement.setString(8, influenceData.getPlanIdle());
        statement.setString(9, influenceData.getInformClient());
        if (influenceData.getCustomerNumber() != null) {
            statement.setString(10, influenceData.getCustomerNumber().toString());
        } else {
            statement.setString(10, "");
        }
        statement.setString(11, influenceData.getServiceType());
        statement.setString(12, influenceData.getAddress());
        statement.setString(13, influenceData.getCustomerName());
        statement.setString(14, influenceData.getSegment());
        statement.setString(15, influenceData.getUnicornSubSegment());
        statement.setString(16, influenceData.getClnSla());

        statement.addBatch();
        time = System.currentTimeMillis();
        if (++j == maxBatchSize) {
            statement.executeBatch();
            con.commit();
            statement.clearBatch();
            j = 0;
            System.out.println("executeBatch finished in: " + (System.currentTimeMillis() - time) + "ms");
        }
    }
}
// 注:原代码未展示finally块的资源释放逻辑

日志现象

前30条:
INSERT INTO hrm_wi_influence_for_ins --> 17ms
executeBatch finished in: 24ms

接下来30条:
--> 10ms
executeBatch finished in: 19ms

后续某批次30条:
--> 6ms
executeBatch finished in: 2947ms

观察到statement.clearBatch()未有效清空批处理,所有行在查询中累积,初始30行,10次操作后变为300行,导致耗时激增。

TomEE数据源配置

JdbcDriver  oracle.jdbc.OracleDriver
JdbcUrl jdbc:oracle:thin:---------
UserName    ---------
Password    ---------
accessToUnderlyingConnectionAllowed = true
jtaManaged = false
DataSourceCreator = tomcat
initialSize=10
maxActive=100
maxIdle=20
minIdle=10
timeBetweenEvictionRunsMillis=34000
minEvictableIdleTimeMillis=55000
validationQuery=SELECT 1 from dual
validationInterval=30000
testOnBorrow=true
removeAbandoned=true
removeAbandonedTimeout=1200
logAbandoned=true

排查思路

  • 验证批处理清空逻辑:

    1. 打印executeBatch()返回的影响行数数组,确认每次是否仅处理30条数据。若返回数组长度大于30,说明批处理未被正确清空。
    2. 替换clearBatch()逻辑:每次执行完批处理后关闭当前PreparedStatement,重新创建新实例继续添加数据。若性能恢复稳定,说明原clearBatch()未生效,可能是驱动或容器的statement缓存问题。
  • 检查JDBC驱动兼容性:

    1. 确认Oracle JDBC驱动8与Oracle 19c的匹配性,排查是否存在批处理相关已知bug。
    2. 尝试升级/降级JDBC驱动至稳定兼容版本,验证问题是否消失。
  • 排查连接池配置:

    1. 添加statementCacheSize=0关闭Tomcat数据源的statement缓存,避免缓存实例残留批处理数据。
    2. 临时关闭removeAbandoned(设为false)、延长validationInterval,观察是否因连接验证或资源回收导致额外开销。
  • 数据库端排查:

    1. 查询Oracle的v$sql视图,查看批量SQL的实际执行计划和绑定参数数量,确认是否存在批处理累积。
    2. 通过v$session_wait查看会话等待事件,排查锁等待、redo日志瓶颈、表空间不足等问题。
  • 代码逻辑补全:

    1. 循环结束后添加判断,执行剩余未达批量大小的批处理数据并提交,避免后续复用statement时累积数据。
    2. 确认finally块中是否正确关闭PreparedStatement和Connection,避免资源泄漏导致连接池耗尽。

关于clearBatch()的说明

根据JDBC规范,clearBatch()用于清空当前PreparedStatement中所有已添加的批处理命令,调用后addBatch()应重新累积新命令。正常情况下,Oracle JDBC驱动的clearBatch()会清空内部维护的批处理数组。若出现未清空的情况,可能是:

  1. 驱动版本存在bug,导致批处理命令未被正确清除;
  2. TomEE/Tomcat的statement缓存机制复用了未清空的实例;
  3. 代码存在线程安全问题(当前代码中statement为方法内局部变量,此可能性较低)。

内容的提问来源于stack exchange,提问作者Sergej Kalva

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 06:34:55