Оптимизация большого количества вставок с помощью JDBC и MySQL - PullRequest
2 голосов
/ 23 сентября 2011

Мне нужно выполнить большое количество вставок (в данном случае 27k), и я хочу найти оптимальное решение для этого. Прямо сейчас это код, который у меня есть. Как вы можете видеть, я использую подготовленные операторы и пакеты и выполняю каждую 1000 (я также пробовал с меньшим числом, таким как 10 и 100, но время снова стало длинным). Одна вещь, которая пропускается в запросе, это то, что существует автоматически сгенерированный идентификатор, если он имеет какое-либо значение для проблемы:

private void parseIndividualReads(String file, DBAccessor db) {
    BufferedReader reader;
    try {
        Connection con = db.getCon();
        PreparedStatement statement = null;
        statement = con.prepareStatement("INSERT INTO `vgsan01_process_log`.`contigs_and_large_singletons` (`seq_id` ,`length` ,`ws_id` ,`num_of_reads`) VALUES (?, ?, ?, ?)");
        long count = 0;
        reader = new BufferedReader(new FileReader(logDir + "/" + file));
        String line;
        while ((line = reader.readLine()) != null) {
            if(count != 0 && count % 1000 == 0)
                statement.executeBatch();

            if (line.startsWith(">")) {
                count++;
                String res[] = parseHeader(line);
                statement.setString(1, res[0]);
                statement.setInt(2, Integer.parseInt(res[1]) );
                statement.setInt(3, id);
                statement.setInt(4, -1);
                statement.addBatch(); 
            }
        }

        statement.executeBatch();
    } catch (FileNotFoundException ex) {
        Logger.getLogger(VelvetStats.class.getName()).log(Level.SEVERE, "Error opening file: " + file, ex);
    } catch (IOException ex) {
        Logger.getLogger(VelvetStats.class.getName()).log(Level.SEVERE, "Error reading from file: " + file, ex);
    } catch (SQLException ex) {
        Logger.getLogger(VelvetStats.class.getName()).log(Level.SEVERE, "Error inserting individual statistics " + file, ex);
    }
}

Любые другие советы относительно того, что может быть изменено для ускорения процесса. Я имею в виду, что один оператор вставки не имеет много информации - я бы сказал, не более 50 символов для всех 4 столбцов

EDIT:

Хорошо, следуя приведенному совету, я перестроил метод следующим образом. Ускорение огромно. Вы даже можете попробовать поиграть со значением 1000, которое может дать лучшие результаты:

private void parseIndividualReads(String file, DBAccessor db) {
    BufferedReader reader;
    PrintWriter writer;
    try {
        Connection con = db.getCon();
        con.setAutoCommit(false);
        Statement st = con.createStatement();
        StringBuilder sb = new StringBuilder(10000);

        reader = new BufferedReader(new FileReader(logDir + "/" + file));
        writer = new PrintWriter(new BufferedWriter(new FileWriter(logDir + "/velvet-temp-contigs", true)), true);
        String line;
        long count = 0;
        while ((line = reader.readLine()) != null) {

            if (count != 0 && count % 1000 == 0) {
                sb.deleteCharAt(sb.length() - 1);
                st.executeUpdate("INSERT INTO `vgsan01_process_log`.`contigs_and_large_singletons` (`seq_id` ,`length` ,`ws_id` ,`num_of_reads`) VALUES " + sb);
                sb.delete(0, sb.capacity());
                count = 0;
            }
            //we basically build a giant VALUES (),(),()... string that we use for insert
            if (line.startsWith(">")) {
                count++;
                String res[] = parseHeader(line);
                sb.append("('" + res[0] + "','" + res[1] + "','" + id + "','" + "-1'" + "),");
            }
        }

        //insert all the remaining stuff
        sb.deleteCharAt(sb.length() - 1);
        st.executeUpdate("INSERT INTO `vgsan01_process_log`.`contigs_and_large_singletons` (`seq_id` ,`length` ,`ws_id` ,`num_of_reads`) VALUES " + sb);
        con.commit();
    } catch (FileNotFoundException ex) {
        Logger.getLogger(VelvetStats.class.getName()).log(Level.SEVERE, "Error opening file: " + file, ex);
    } catch (IOException ex) {
        Logger.getLogger(VelvetStats.class.getName()).log(Level.SEVERE, "Error reading from file: " + file, ex);
    } catch (SQLException ex) {
        Logger.getLogger(VelvetStats.class.getName()).log(Level.SEVERE, "Error working with mysql", ex);
    }
}

Ответы [ 3 ]

3 голосов
/ 23 сентября 2011

У вас есть другие решения.

  1. Используйте LOAD DATE INFILE из mySQL Documentation .
  2. Если вы хотите сделать это на Java, используйте только один операторкоторый вставляет 1000 значений в 2 порядке: "INSERT INTO mytable (col1, col2) VALUES (val1, val2), (val3, val4), ..."

Но я бы рекомендовал 1-е решение.

1 голос
/ 23 сентября 2011

В зависимости от структуры ваших данных у вас есть потенциальная ошибка в логике «Выполнять пакет каждые 1000 итераций».

Если частота строк, начинающихся с «>», низкая, то может возникнуть случай, когда произойдет следующее (загрузка множества ненужных вызовов executeBatch:

line in data file      events in program
-----------------------------------------------------------
> some data            (count=999)
> some more data       (count=1000)
another line           (execute batch, count=1000)
more unprocessed       (execute batch, count=1000)
some more              (execute batch, count=1000)

Так что я бы переместил if(count != 0 && count % 1000 == 0) внутрь блока if (line.startsWith(">")).

Заметьте, я уверен, что это может произойти в ваших данных или в какой степени это ускорится.

1 голос
/ 23 сентября 2011

Самый быстрый способ сделать то, что вы хотите сделать, это загрузить прямо из файла (http://dev.mysql.com/doc/refman/5.5/en/load-data.html).

Есть некоторые проблемы с загрузкой из файла - во-первых, файл должен быть доступен для чтения на сервере, что часто не так. Обработка ошибок может быть проблемой, и вы можете получить непоследовательные или неполные данные, если данные в вашем файле не соответствуют ожиданиям схемы.

Это также зависит от того, что является фактическим узким местом - если в таблице, в которую вы вставляете, много лотов, вам лучше использовать комбинацию вставки с задержкой (http://dev.mysql.com/doc/refman/5.5/en/insert-delayed.html).

Официальная строка по ускорению вставок здесь: http://dev.mysql.com/doc/refman/5.5/en/insert-speed.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...