Улучшение производительности JDBC

Я выполняю следующий набор инструкций в своем приложении java. Он подключается к базе данных оракула.

stat=connection.createStatement();
stat1=commection.createstatement();
ResultSet rs = stat.executeQuery(BIGQUERY);
while(rs.next()) {
 obj1.setAttr1(rs.getString(1));
 obj1.setAttr2(rs.getString(1));
 obj1.setAttr3(rs.getString(1));
 obj1.setAttr4(rs.getString(1));

 ResultSet rs1 = stat1.executeQuery(SMALLQ1);
 while(rs1.next()) {
 obj1.setAttr5(rs1.getString(1));
 }

 ResultSet rs2 = stat1.executeQuery(SMALLQ2);
 while(rs2.next()) {
 obj1.setAttr6(rs2.getString(1));

 }
 .
 .
 .
 LinkedBlockingqueue.add(obj1);
 }
 //all staements and connections close

BIGQUERY возвращает около 4,5 миллионов записей, и для каждой записи мне приходится выполнять меньшие запросы, число которых BIGQUERY 14. Каждый маленький запрос имеет три внутренних оператора объединения.

Мое многопоточное приложение теперь может обрабатывать 90 000 за один час. Но мне, возможно, придется ежедневно запускать код, поэтому я хочу обработать все записи за 20 часов. Я использую около 200 потоков, которые обрабатывают вышеуказанный код и сохраняют записи в связанной блокирующей очереди.

Увеличивает ли количество потоков вслепую помогает повысить производительность или есть ли другой способ, с помощью которого я могу повысить производительность наборов результатов?

PS: Я не могу опубликовать запрос здесь, но я уверен, что все запросы оптимизированы.

1 ответ

Чтобы улучшить производительность JDBC для вашего сценария, вы можете применить некоторые изменения.

Как вы увидите, все эти изменения могут значительно ускорить вашу задачу.

1. Использование пакетных операций.

Вы можете прочитать свой большой запрос и сохранить результаты в каком-то буфере. И только когда буфер заполнен, вы должны запустить подзапрос для всех данных, собранных в буфере. Это значительно сокращает количество выполняемых SQL-операторов.

static final int BATCH_SIZE = 1000; 
List<mydata> buffer = new ArrayList<>(BATCH_SIZE);

while (rs.hasNext()) {

 MyData record = new MyData( rs.getString(1), ..., rs.getString(4) );
 buffer.add( record );

 if (buffer.size() == BATCH_SIZE) {
 processBatch( buffer );
 } 
}

void processBatch( List<mydata> buffer ) {

 String sql = "select ... where X and id in (" + getIDs(buffer) + ")";
 stat1.executeQuery(sql); // query for all IDs in buffer
 while(stat1.hasNext()) { ... }
 ... 
}
</mydata></mydata>

2. Использование эффективных карт для хранения контента из множества избранных.

Если ваши записи не так велики, вы можете сохранить их сразу за 4-мя столом.

Я использовал этот подход много раз для ночных процессов (без обычных пользователей). Такой подход может потребовать много памяти кучи (т.е. 100 МБ - 1 ГБ), но гораздо быстрее, чем подход 1).

Для этого вам нужна эффективная реализация карты, т.е. - gnu.trove.map.TIntObjectMap (и т.д.), Которая намного лучше отображает стандартную библиотеку Java.

final TIntObjectMap<mydata> map = new TIntObjectHashMap<mydata>(10000, 0.8f);

// query 1
while (rs.hasNext()) {
 MyData record = new MyData( rs.getInt(1), rs.getString(2), ..., rs.getString(4) );
 map.put(record.getId(), record);
}

// query 2
while (rs.hasNext()) {
 int id = rs.getInt(1); // my data id
 String x = rs.getString(...);
 int y = rs.getInt(...);

 MyData record = map.get(id);
 record.add( new MyDetail(x,y) );
}

// query 3
// same pattern as query 2 
</mydata></mydata>

После этого у вас есть карта, заполненная всеми собранными данными. Вероятно, выделено много памяти. Вот почему вы можете использовать этот метод, только если у вас есть такие ресурсы.

Другой темой является то, как писать классы MyData и MyDetail как можно меньше. Вы можете использовать некоторые трюки:

  1. сохраняя 3 целых числа (с ограниченным диапазоном) в 1 длинной переменной (с использованием утилиты для смещения битов)
  2. сохранение объектов Date как integer (yymmdd)
  3. вызов str.intern() для каждой строки, извлеченной из БД

3. Сделки

Если вам нужно сделать некоторые обновления или вставки, чем 4 миллиона записей, слишком много для обработки транзакций. Это слишком много для большинства конфигураций баз данных. Использовать подход 1) и совершить транзакцию для каждой партии. На каждой новой вставленной записи вы можете иметь что-то вроде RUN_ID, и если все будет хорошо, вы можете пометить этот идентификатор RUN_ID как успешный.

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

4. Размер выборки Jdbc.

Когда вы загружаете много записей из базы данных, очень важно установить правильный размер выборки в вашем соединении jdbc. Это уменьшает количество физических обращений к сокету базы данных и ускоряет процесс.

Пример:

// jdbc
statement.setFetchSize(500);

// spring 
JdbcTemplate jdbc = new JdbcTemplate(datasource);
jdbc.setFetchSize(500);

Здесь вы можете найти некоторые ориентиры и шаблоны для использования размера выборки:

http://makejavafaster.blogspot.com/2015/06/jdbc-fetch-size-performance.html

5. Подготовлено

Используйте PreparedStatement, а не Statement.

6. Количество операторов sql.

Всегда старайтесь минимизировать количество операторов sql, отправляемых в базу данных.

licensed under cc by-sa 3.0 with attribution.