Databases 10 min read

Why MyBatis‑Plus saveBatch Is Slow and How to Speed It Up 30×

This article analyzes the poor performance of MyBatis‑Plus saveBatch, compares it with single inserts, manual SQL batching, and JDBC executeBatch, and shows how enabling the rewriteBatchedStatements flag can dramatically improve batch insert speed.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Why MyBatis‑Plus saveBatch Is Slow and How to Speed It Up 30×

Hello, I am Su San. While load‑testing a set of APIs I discovered that batch saving to the database was much slower than expected.

The project uses

mybatis-plus

and its

saveBatch

method. Looking at the source code, the implementation loops over each record and calls

sqlSession.insert

for every item, then flushes after a certain count.

1000 rows, inserted one by one
@Test
void MybatisPlusSaveOne() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("mybatis plus save one");
        for (int i = 0; i < 1000; i++) {
            OpenTest openTest = new OpenTest();
            openTest.setA("a" + i);
            openTest.setB("b" + i);
            openTest.setC("c" + i);
            openTest.setD("d" + i);
            openTest.setE("e" + i);
            openTest.setF("f" + i);
            openTest.setG("g" + i);
            openTest.setH("h" + i);
            openTest.setI("i" + i);
            openTest.setJ("j" + i);
            openTest.setK("k" + i);
            // one‑by‑one insert
            openTestService.save(openTest);
        }
        sqlSession.commit();
        stopWatch.stop();
        log.info("mybatis plus save one:" + stopWatch.getTotalTimeMillis());
    } finally {
        sqlSession.close();
    }
}

The execution time for inserting 1000 rows one by one was 121,011 ms .

1000 rows inserted with MyBatis‑Plus saveBatch
@Test
void MybatisPlusSaveBatch() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        List<OpenTest> openTestList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            OpenTest openTest = new OpenTest();
            openTest.setA("a" + i);
            openTest.setB("b" + i);
            openTest.setC("c" + i);
            openTest.setD("d" + i);
            openTest.setE("e" + i);
            openTest.setF("f" + i);
            openTest.setG("g" + i);
            openTest.setH("h" + i);
            openTest.setI("i" + i);
            openTest.setJ("j" + i);
            openTest.setK("k" + i);
            openTestList.add(openTest);
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("mybatis plus save batch");
        // batch insert
        openTestService.saveBatch(openTestList);
        sqlSession.commit();
        stopWatch.stop();
        log.info("mybatis plus save batch:" + stopWatch.getTotalTimeMillis());
    } finally {
        sqlSession.close();
    }
}

Using

saveBatch

reduced the time to 59,927 ms , roughly twice as fast as the single‑insert loop.

Next, I tried a manual SQL‑concatenation approach.

1000 rows inserted with manually concatenated SQL
@Test
void MapperSaveBatch() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        List<OpenTest> openTestList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            OpenTest openTest = new OpenTest();
            openTest.setA("a" + i);
            openTest.setB("b" + i);
            openTest.setC("c" + i);
            openTest.setD("d" + i);
            openTest.setE("e" + i);
            openTest.setF("f" + i);
            openTest.setG("g" + i);
            openTest.setH("h" + i);
            openTest.setI("i" + i);
            openTest.setJ("j" + i);
            openTest.setK("k" + i);
            openTestList.add(openTest);
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("mapper save batch");
        // manual batch insert
        openTestMapper.saveBatch(openTestList);
        sqlSession.commit();
        stopWatch.stop();
        log.info("mapper save batch:" + stopWatch.getTotalTimeMillis());
    } finally {
        sqlSession.close();
    }
}

This method finished in 2,275 ms**, a 26‑fold improvement over MyBatis‑Plus saveBatch.

I also benchmarked plain JDBC

executeBatch

:

1000 rows inserted with JDBC executeBatch
@Test
void JDBCSaveBatch() throws SQLException {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    Connection connection = sqlSession.getConnection();
    connection.setAutoCommit(false);
    String sql = "insert into open_test(a,b,c,d,e,f,g,h,i,j,k) values(?,?,?,?,?,?,?,?,?,?,?)";
    PreparedStatement statement = connection.prepareStatement(sql);
    try {
        for (int i = 0; i < 1000; i++) {
            statement.setString(1, "a" + i);
            statement.setString(2, "b" + i);
            statement.setString(3, "c" + i);
            statement.setString(4, "d" + i);
            statement.setString(5, "e" + i);
            statement.setString(6, "f" + i);
            statement.setString(7, "g" + i);
            statement.setString(8, "h" + i);
            statement.setString(9, "i" + i);
            statement.setString(10, "j" + i);
            statement.setString(11, "k" + i);
            statement.addBatch();
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("JDBC save batch");
        statement.executeBatch();
        connection.commit();
        stopWatch.stop();
        log.info("JDBC save batch:" + stopWatch.getTotalTimeMillis());
    } finally {
        statement.close();
        sqlSession.close();
    }
}

The JDBC batch took 55,663 ms**, similar to MyBatis‑Plus saveBatch because both ultimately use the same driver logic.

Investigating the MySQL driver revealed the

rewriteBatchedStatements

flag. When set to

true

, the driver rewrites a batch of inserts into a single statement like

INSERT INTO xxx VALUES (a),(b),(c)…

, achieving the same effect as manual concatenation.

By adding

rewriteBatchedStatements=true

to the JDBC URL, the performance improved dramatically:

mybatis‑plus

saveBatch

with the flag: 2,589 ms

JDBC

executeBatch

with the flag: 324 ms

Thus, for any batch‑insert requirement, enabling

rewriteBatchedStatements

yields a huge speedup, and manual SQL concatenation should be used with careful batch sizing.

PerformanceMySQLJDBCMyBatis-PlusBatch InsertrewriteBatchedStatements
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.