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

Spring Boot搭配MariaDB驱动、AWS JDBC包装器及HikariCP:批量执行仅单语句生效

问题描述
  • 使用PreparedStatement.executeBatch()执行批量插入/更新时,多条语句批量执行失效(无记录插入/更新或报错),但单条语句调用executeBatch()时执行正常
  • 批量执行过程中出现Socket超时错误

环境信息

  • Spring Boot版本:3.4.5
  • MariaDB JDBC驱动版本:3.4.2
  • AWS Advanced JDBC包装器版本:2.5.2
  • Aurora MySQL版本:2.7.12
  • HikariCP版本:5.1.0
  • JDBC URL示例:jdbc:aws-wrapper:mariadb://AuroraHost:AuroraPort/Schema?rewriteBatchedStatements=true&useSSL=false

代码示例

public Integer saveBulkDataInTableData(List<Schema> listOfCandidates) throws SQLException {
    Integer numberOfRecordsSaved = 0;
            
    int[] batchExecutionResult;
    if (!listOfCandidates.isEmpty()) {
        try(Connection conn = writeDataSource.getConnection(); PreparedStatement preparedStatement = conn.prepareStatement("INSERT INTO table01 (Id, isProcessed, lastModifiedDate) VALUES (?,?,?)")) {
            if (conn == null || conn.isClosed()) {
                LOGGER.error("Database connection is not available or is closed.");
                throw new SQLException("Database connection is not available or is closed.");
            }
            conn.setAutoCommit(false);
            int batchSize = 2000;
            for (int i = 0; i < listOfCandidates.size(); i++) {
                conn.setAutoCommit(false);
                preparedStatement.setString(1, listOfCandidates.get(i).getId());
                preparedStatement.setString(2, "N");
                preparedStatement.setString(3, listOfCandidates.get(i).getActivityDate());
                preparedStatement.addBatch();
                if ((i + 1) % batchSize == 0) {
                    LOGGER.debug("Saving batch at record number {}", i + 1);
                    try {
                        preparedStatement.executeBatch();
                        preparedStatement.clearBatch();
                        conn.commit();
                    } catch (Exception exception) {
                        LOGGER.error("Batch with record number {} failed", i + 1, exception);
                        conn.rollback();
                    }
                }
            }
            // 执行批量中剩余的记录
            try {
                batchExecutionResult = preparedStatement.executeBatch();
                numberOfRecordsSaved = numberOfRecordsSaved + batchExecutionResult.length;
                conn.commit();
            } catch (Exception exception) {
                LOGGER.error("Records remaining in the saveBulkDataInTableData batch failed", exception);
                conn.rollback();
            } finally {
                conn.setAutoCommit(true);
            }
        }
    }
    return numberOfRecordsSaved;
}

错误日志

主错误栈

WriterConnectionPool - Connection org.mariadb.jdbc.Connection@457512b marked as broken because of SQLSTATE(XXXXX), ErrorCode(-1)|com.zaxxer.hikari.pool.ProxyConnection.checkException|178||||java.sql.BatchUpdateException: (conn=6066945) Socket timout error at org.mariadb.jdbc.export.ExceptionFactory.createBatchUpdate(ExceptionFactory.java:221) at org.mariadb.jdbc.client.impl.StandardClient.executePipeline(StandardClient.java:999) at org.mariadb.jdbc.Statement.executeInternalBatchPipeline(Statement.java:1625) at org.mariadb.jdbc.Statement.executeBatch(Statement.java:766) at com.zaxxer.hikari.pool.ProxyStatement.executeBatch(ProxyStatement.java:127) at com.zaxxer.hikari.pool.HikariProxyStatement.executeBatch(HikariProxyStatement.java) at com.xxx.container.xxx.xxxx.xxxxxx.xxx.xxxxxxxxxxxxx.savetestData(xxxxxxxxxxxxx.java:91) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728)

根因错误栈

Caused by: java.sql.SQLNonTransientConnectionException: (conn=6066945) Socket timout error
at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:300)
at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:366)
at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1258)
at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1173)
at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:1092)
at org.mariadb.jdbc.client.impl.StandardClient.executePipeline(StandardClient.java:932)
... 64 more
Caused by: java.net.SocketTimeoutException: Read timed out
at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:288)
at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:314)
at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:355)
at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:808)
at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966)
at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:244)
at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:343)
at org.mariadb.jdbc.client.socket.impl.PacketReader.readReusablePacket(PacketReader.java:62)
at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:164)
at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1234)

解决方案

1. 调整批量大小与超时参数

当前2000条的批量大小可能超出Aurora的单批次处理能力,导致超时:

  • 降低批量大小至500-1000条,减少单批次数据量
  • 在JDBC URL中增加Socket超时配置,例如socketTimeout=60000(设置为60秒),避免因处理时间过长断开连接

2. 修复代码冗余

循环内重复调用conn.setAutoCommit(false);属于冗余操作,将其移至循环外,减少不必要的连接状态变更。

3. 验证组件兼容性

AWS JDBC包装器与MariaDB驱动的版本组合可能存在批量处理兼容性问题:

  • 升级AWS Advanced JDBC包装器至最新稳定版(如2.6.x系列)
  • 确认rewriteBatchedStatements=true参数被AWS包装器正确传递至MariaDB驱动,该参数是驱动端优化批量插入的关键配置

4. 优化HikariCP连接池配置

确保连接池超时设置大于Socket超时,避免连接池提前回收连接:

spring.datasource.hikari.connection-timeout=300000
spring.datasource.hikari.socket-timeout=60000

5. 排查Aurora实例性能

Socket超时可能由Aurora资源瓶颈导致:

  • 查看Aurora实例的CPU、内存使用率,确认是否存在资源不足情况
  • 检查慢查询日志,优化批量插入语句(例如调整相关索引)

内容的提问来源于stack exchange,提问作者vishal gawde

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 18:07:01