Производительность пакетной вставки JDBC

Мне нужно вставить пару сотен миллионов записей в mysql db. Я участвую в выпуске 1 миллион за один раз. См. Мой код ниже. Кажется, он медленный. Есть ли способ его оптимизировать?

try {
        // Disable auto-commit
        connection.setAutoCommit(false);

        // Create a prepared statement
        String sql = "INSERT INTO mytable (xxx), VALUES(?)";
        PreparedStatement pstmt = connection.prepareStatement(sql);

        Object[] vals=set.toArray();
        for (int i=0; i<vals.length; i++) {
            pstmt.setString(1, vals[i].toString());
            pstmt.addBatch();
        }

        // Execute the batch
        int [] updateCounts = pstmt.executeBatch();
        System.out.append("inserted "+updateCounts.length);

Ответ 1

У меня была аналогичная проблема с производительностью с mysql и она была решена путем установки свойств useServerPrepStmts и rewriteBatchedStatements в URL-адресе подключения.

Connection c = DriverManager.getConnection("jdbc:mysql://host:3306/db?useServerPrepStmts=false&rewriteBatchedStatements=true", "username", "password");

Ответ 2

Я хотел бы расширить ответ на Bertil, поскольку я экспериментировал с параметрами URL-адреса подключения.

rewriteBatchedStatements=true является важным параметром. useServerPrepStmts по умолчанию уже ложно, и даже изменение его на true не имеет большого значения в плане производительности пакетной вставки.

Теперь я думаю, что настало время написать, как rewriteBatchedStatements=true значительно улучшает производительность. Он делает это через rewriting of prepared statements for INSERT into multi-value inserts when executeBatch() (Источник). Это означает, что вместо отправки следующих операторов n INSERT на сервер mysql каждый раз вызывается executeBatch():

INSERT INTO X VALUES (A1,B1,C1)
INSERT INTO X VALUES (A2,B2,C2)
...
INSERT INTO X VALUES (An,Bn,Cn)

Он отправил бы один оператор INSERT:

INSERT INTO X VALUES (A1,B1,C1),(A2,B2,C2),...,(An,Bn,Cn)

Вы можете наблюдать за ним, переключаясь на журнал mysql (через SET global general_log = 1), который будет записывать в файл каждый оператор, отправленный на сервер mysql.

Ответ 3

Вы можете вставлять несколько строк в один оператор insert, делая несколько тысяч за раз, может значительно ускорить процесс, то есть вместо того, чтобы делать, например, 3 вставки формы INSERT INTO tbl_name (a,b,c) VALUES(1,2,3);, вы делаете INSERT INTO tbl_name (a,b,c) VALUES(1,2,3),(1,2,3),(1,2,3); (может быть, JDBC.addBatch() делает подобную оптимизацию сейчас - хотя mysql addBatch раньше не оптимизировался и просто выдавал отдельные запросы в любом случае - я не знаете, если это все еще имеет место с недавними драйверами)

Если вам действительно нужна скорость, загрузите свои данные из файла с разделителями-запятыми с помощью LOAD DATA INFILE, мы получаем около 7-8 раз ускорение что против десятков миллионов вставок.

Ответ 4

Если:

  • Это новая таблица или сумма, которая будет вставлена, больше, чем уже вставленные данные
  • В таблице есть индексы
  • Вам не нужен другой доступ к таблице во время вставки

Затем ALTER TABLE tbl_name DISABLE KEYS может значительно улучшить скорость ваших вставок. Когда вы закончите, запустите ALTER TABLE tbl_name ENABLE KEYS, чтобы начать создавать индексы, что может занять некоторое время, но не так долго, как делать это для каждой вставки.

Ответ 5

Вы можете попробовать использовать объект DDBulkLoad.

// Get a DDBulkLoad object
DDBulkLoad bulkLoad = DDBulkLoadFactory.getInstance(connection);
bulkLoad.setTableName("mytable");
bulkLoad.load("data.csv");

Ответ 6

try {
        // Disable auto-commit
        connection.setAutoCommit(false);
        int maxInsertBatch = 10000;     
        // Create a prepared statement
        String sql = "INSERT INTO mytable (xxx), VALUES(?)";
        PreparedStatement pstmt = connection.prepareStatement(sql);

        Object[] vals=set.toArray();
        int count = 1;
        for (int i=0; i<vals.length; i++) {
            pstmt.setString(1, vals[i].toString());
            pstmt.addBatch();
            if(count%maxInsertBatch == 0){
                 pstmt.executeBatch();
            }
            count++;
        }

        // Execute the batch
        pstmt.executeBatch();
        System.out.append("inserted "+count);