Использование ScrollableResults Hibernate для медленного чтения 90 миллионов записей - PullRequest
50 голосов
/ 13 мая 2010

Мне просто нужно прочитать каждую строку в таблице в моей базе данных MySQL, используя Hibernate, и записать файл на ее основе. Но есть 90 миллионов строк, и они довольно большие. Таким образом, казалось, что было бы целесообразно следующее:

ScrollableResults results = session.createQuery("SELECT person FROM Person person")
            .setReadOnly(true).setCacheable(false).scroll(ScrollMode.FORWARD_ONLY);
while (results.next())
    storeInFile(results.get()[0]);

Проблема в том, что описанное выше попытается загрузить все 90 миллионов строк в ОЗУ, прежде чем перейти к циклу while ... и это уничтожит мою память с помощью OutOfMemoryError: исключения пространства кучи Java: (..

Итак, я думаю, ScrollableResults - это не то, что я искал? Как правильно справиться с этим? Я не против, если этот цикл занимает дни (ну, я бы с удовольствием это сделал).

Полагаю, единственный другой способ справиться с этим - использовать setFirstResult и setMaxResults для перебора результатов и просто использовать обычные результаты Hibernate вместо ScrollableResults. Такое ощущение, что это будет неэффективно, и начнёт занимать смехотворно много времени, когда я вызову setFirstResult для 89-миллионной строки ...

ОБНОВЛЕНИЕ: setFirstResult / setMaxResults не работает, оказывается, что до смещения, как я боялся, уходит необычайно много времени. Здесь должно быть решение! Разве это не довольно стандартная процедура? Я готов отказаться от Hibernate и использовать JDBC или что-то еще.

ОБНОВЛЕНИЕ 2: предложенное мной решение, которое работает нормально, не очень, в основном имеет вид:

select * from person where id > <offset> and <other_conditions> limit 1

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

Ответы [ 12 ]

0 голосов
/ 28 октября 2013

недавно я работал над такой проблемой и написал блог о том, как решить эту проблему. очень похоже, я надеюсь быть полезным для любого. я использую ленивый подход списка с частичным adquisition. я заменил предел и смещение или нумерацию запроса на нумерацию страниц вручную. В моем примере select возвращает 10 миллионов записей, я получаю их и вставляю в «временную таблицу»:

create or replace function load_records ()
returns VOID as $$
BEGIN
drop sequence if exists temp_seq;
create temp sequence temp_seq;
insert into tmp_table
SELECT linea.*
FROM
(
select nextval('temp_seq') as ROWNUM,* from table1 t1
 join table2 t2 on (t2.fieldpk = t1.fieldpk)
 join table3 t3 on (t3.fieldpk = t2.fieldpk)
) linea;
END;
$$ language plpgsql;

после этого я могу разбивать на страницы без подсчета каждой строки, но используя назначенную последовательность:

select * from tmp_table where counterrow >= 9000000 and counterrow <= 9025000

С точки зрения Java, я реализовал эту нумерацию страниц посредством частичного размещения с ленивым списком. это список, который выходит из списка Abstract и реализует метод get (). Метод get может использовать интерфейс доступа к данным для продолжения получения следующего набора данных и освобождения кучи памяти:

@Override
public E get(int index) {
  if (bufferParcial.size() <= (index - lastIndexRoulette))
  {
    lastIndexRoulette = index;
    bufferParcial.removeAll(bufferParcial);
    bufferParcial = new ArrayList<E>();
        bufferParcial.addAll(daoInterface.getBufferParcial());
    if (bufferParcial.isEmpty())
    {
        return null;
    }

  }
  return bufferParcial.get(index - lastIndexRoulette);<br>
}

с другой стороны, интерфейс доступа к данным использует запрос для разбивки на страницы и реализует один метод для последовательной итерации, каждые 25000 записей для завершения всего этого.

результаты для этого подхода можно увидеть здесь http://www.arquitecturaysoftware.co/2013/10/laboratorio-1-iterar-millones-de.html

0 голосов
/ 13 мая 2010

Ранее я успешно использовал функцию прокрутки Hibernate, не читая весь набор результатов. Кто-то сказал, что MySQL не выполняет настоящие курсоры прокрутки, но утверждает, что основывается на JDBC dmd.supportsResultSetType (ResultSet.TYPE_SCROLL_INSENSITIVE) и поиск вокруг кажется, что другие люди использовали это. Убедитесь, что он не кэширует объекты Person в сеансе - я использовал его в SQL-запросах, где не было сущности для кэширования. Вы можете вызвать evict в конце цикла, чтобы убедиться, или протестировать с SQL-запросом. Также поиграйте с setFetchSize, чтобы оптимизировать количество поездок на сервер.

...