Высокопроизводительная гибернационная вставка
Я работаю над чувствительной к задержке частью приложения, в основном я получу сетевое событие, преобразует данные и затем вставлю все данные в БД. После профилирования я вижу, что в основном все мое время тратится на сохранение данных. вот код
private void insertAllData(Collection<Data> dataItems)
{
long start_time = System.currentTimeMillis();
long save_time = 0;
long commit_time = 0;
Transaction tx = null;
try
{
Session s = HibernateSessionFactory.getSession();
s.setCacheMode(CacheMode.IGNORE);
s.setFlushMode(FlushMode.NEVER);
tx = s.beginTransaction();
for(Data data : dataItems)
{
s.saveOrUpdate(data);
}
save_time = System.currentTimeMillis();
tx.commit();
s.flush();
s.clear();
}
catch(HibernateException ex)
{
if(tx != null)
tx.rollback();
}
commit_time = System.currentTimeMillis();
System.out.println("Save: " + (save_time - start_time));
System.out.println("Commit: " + (commit_time - save_time));
System.out.println();
}
Размер коллекции всегда меньше 20. Вот временные данные, которые я вижу:
Save: 27
Commit: 9
Save: 27
Commit: 9
Save: 26
Commit: 9
Save: 36
Commit: 9
Save: 44
Commit: 0
Это смущает меня. Я полагаю, что save
должно быть быстрым и все время должно быть потрачено на commit
, но явно я не прав. Я также попытался удалить транзакцию (это не очень необходимо), но я видел худшие времена... Я установил hibernate.jdbc.batch_size=20...
Я могу ожидать получения до 500 сообщений в секунду, поэтому мне нужно, чтобы обработка одного сообщения была менее 20 миллисекунд.
мне нужно, чтобы эта операция была как можно быстрее, в идеале должна быть только одна поездка в базу данных. Как я могу это сделать?
3 ответа
Переместите генерацию первичного ключа от автоматического увеличения на стороне сервера. Ваш Java-код должен нести ответственность за генерацию PK, чтобы избежать обходов.
Для приличной производительности массовой вставки вам нужен метод, которому не нужно будет обращаться к базе данных при каждом вызове saveOrUpdate. Использование UUID в качестве первичного ключа или реализация HiLo могут помочь в этом. В противном случае, нет никакой массовой вставки.
Для обеспечения как производительности, так и совместимости с другими внешними системами наилучшим выбором являются оптимизаторы pooled или pooled-lo.
Честно говоря, я не знаю, на что можно разумно сделать вывод из вашего теста и из "мер", которые вы показываете (я подозреваю, что из-за разминки много накладных расходов, коллекция очень маленькая, а образец очень маленький).
В любом случае, я могу вам сказать, что ваш текущий код не будет масштабироваться, и вы, скорее всего, собираетесь взорвать сессию при передаче большей коллекции. Вам необходимо регулярно очищать и очищать сеанс (каждые 20 записей, если размер пакета равен 20).
На самом деле, я рекомендую прочитать всю главу 13. Пакетная обработка.
Некоторые основные вещи:
- У вас есть триггеры или ограничения внешнего ключа без индекса?
- Есть ли у вас пакетные драйверы?
- Находятся ли ваши драйверы в пакетном режиме (см. Hibernate.jdbc.batch_size из справочника Паскаля)?
- Какие-нибудь индексы в ваших таблицах (если у вас много индексов, иногда это может замедлить вставку)?
Пакетирование является частью JDBC 2.0, оно позволяет вам выполнять несколько операторов в "пакете"; Идея состоит в том, чтобы уменьшить задержку прохождения туда-обратно (вы можете выполнить несколько пакетов за транзакцию)
Statement stmt = dbCon.createStatement("insert into DataTable values (?,?,?)");
stmt.setInt(1, x1); stmt.setInt(2, x2), stmt.setString(3, "some value");
stmt.addBatch();
...
stmt.setInt(1, x2); stmt.setInt(2, x3), stmt.setString(3, "some other value");
stmt.addBatch();
stmt.executeBatch();
dbCon.commit();
Вы, вероятно, можете использовать это в качестве контрольного теста. Я бы также посмотрел на SQL, который генерирует hibernate, чтобы увидеть, выполняет ли он запрос на вставку, чтобы получить сгенерированные идентификаторы.