Каковы вероятные причины проблем с производительностью StringBuilder и ResultSet

Я перебираю ResultSet в Java; который для целей тестирования возвращает около 30 строк с 17 столбцами (все строковые данные) на строку. Я вручную создаю XML-строку из результатов с помощью StringBuilder, и цикл буквально занимает около 36 секунд, чтобы завершить эти итерации.

Примечание. Я понимаю, что это не лучший способ получить XML из базы данных или даже лучший способ получить XML из ResultSet, но мне все равно любопытно, что медленная производительность.

Обновление: В соответствии с ответами на данный момент я должен решить следующее: время выполнения запроса составляет менее секунды, и я выполнял System.currentTimeMillis() до и после каждого раздела моего кода. чтобы сузить это. 36 секунд полностью находятся в коде ниже.

ResultSetMetaData rsmeta = rset.getMetaData();
StringBuilder resultBuilder = new StringBuilder();
resultBuilder.append("<?xml version=\"1.0\" ?><ROWSET>");
if(numColumns != 0){   
   while (rset.next()) {
      resultBuilder.append("<ROW>");
      for (int i = 0; i <= numColumns -1;i++) {
         columnName = rsmeta.getColumnName(i+1);
         resultBuilder.append("<");
         resultBuilder.append(columnName);
         resultBuilder.append(">");
         resultBuilder.append(rset.getString(i+1));
         resultBuilder.append("</");
         resultBuilder.append(columnName);
         resultBuilder.append(">");
      }
      resultBuilder.append("</ROW>");
      numRows += 1;
   }
}
else {
   stmt.close();
   wsConn.close();
   return "No Results";
}

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

StringBuilder resultBuilder = new StringBuilder();
resultBuilder.append("<?xml version=\"1.0\" ?><ROWSET>");
if(numColumns != 0){   
   while (rset.next()) {
      resultBuilder.append("<ROW>");
      for (int i = 0; i <= numColumns -1;i++) {
         //columnName = rsmeta.getColumnName(i+1);
         resultBuilder.append("<");
         resultBuilder.append("TestColumnName");
         resultBuilder.append(">");
         //resultBuilder.append(rset.getString(i+1));
         resultBuilder.append("TestData");
         resultBuilder.append("</");
         resultBuilder.append("TestColumnName");
         resultBuilder.append(">");
      }
      resultBuilder.append("</ROW>");
      numRows += 1;
   }
}
else {
   stmt.close();
   wsConn.close();
   return "No Results";
}  

Последний тест, который я провел, устранив все остальное, состоял в том, чтобы заменить тест while реалистичным количеством итераций (160, максимальное количество строк, возвращенных из небольших тестов, которые я делал ранее). Теперь возникает вопрос, что может быть такого в этом наборе результатов, что вызывает такое замедление.

while (numRows <= 160) {
// same as above
}

Обновление: Как и предполагалось, я закрою этот вопрос, так как заголовок не отражает направление, в котором возникла проблема.


person Jeff Dalley    schedule 02.02.2010    source источник
comment
Я почти уверен, что ваша проблема здесь не в StringBuilder. Попробуйте удалить все, что упоминает StringBuilder, и оставить только циклы и вызовы getColumnName() и getString().   -  person Joachim Sauer    schedule 02.02.2010
comment
вы выполняете тест производительности для подключения к базе данных. Как вы можете быть уверены, что узким местом не является база данных?   -  person chburd    schedule 02.02.2010
comment
При обновлении: запрос — это только первая часть операции с базой данных. набор результатов часто является фасадом для курсора базы данных.   -  person Arne Burmeister    schedule 02.02.2010
comment
Это интересно, Арне, я не знал об этом. Хм...   -  person Jeff Dalley    schedule 02.02.2010
comment
Можете ли вы поместить System.currentTimeMillis() до и после строки ResultSetMetaData rsmeta = rset.getMetaData(); затем еще один после блока кода. Я подозреваю, что это построение набора результатов вызывает задержку.   -  person Paul    schedule 02.02.2010
comment
Почему бы вам не попробовать поместить имена столбцов и данные в какую-нибудь структуру данных и попробовать тот же код, извлекающий значения String из списка, а не из базы данных? Я думаю, вы обнаружите, что это довольно быстро. (Кроме того, currentTimeMillis() предназначен для настенных часов, nanoTime() предназначен для синхронизации).   -  person ColinD    schedule 02.02.2010
comment
После вашего последнего обновления ваша озабоченность не совпадает с вашим первоначальным вопросом, его заголовком и ответами. Я предлагаю закрыть этот вопрос и задать другой для вашей новой заботы.   -  person KLE    schedule 04.02.2010
comment
Вопрос для более опытных пользователей SO: Когда вы говорите «закрыть», вы имеете в виду выбор ответа или удаление вопроса, поскольку он стал больше касаться производительности ResultSet/Database, чем StringBuilder. Спасибо   -  person Jeff Dalley    schedule 04.02.2010


Ответы (9)


Я очень сомневаюсь, что здесь виноват StringBuilder. Java использует его широко, я использовал его широко, я переписал его для своего собственного вида JVM, и в основном он всегда может потреблять сотни миллионов символов в секунду.

Я думаю, что ваша проблема связана с самим доступом к базе данных. Когда вы выполняете запрос и получаете ResultSet, это не обязательно означает, что все данные были получены и внутренне преобразованы в простое в управлении представление в памяти. В зависимости от реализации базы данных (и ее драйвера JDBC) ResultSet может быть обещанием ряда результатов, которые динамически извлекаются при вызове методов ResultSet.next() и ResultSet.getString().

Попробуйте просто изучить результаты, обзвонив все свои next() и getString(), но не сохраняя полученные данные в свой StringBuilder. Если это все еще занимает 36 секунд, то StringBuilder невиновен (во что я твердо верю).

person Thomas Pornin    schedule 02.02.2010
comment
Я выбрал это как ответ, потому что он эффективно описывает проблему, обнаруженную в ResultSet.next(), которая фактически описывает узкое место в моей процедуре Query/Store. - person Jeff Dalley; 04.02.2010

Я думаю, что ваш note2 говорит сам за себя.

Время теряется не в StringBuilder, а где-то еще...


columnName = rsmeta.getColumnName(i+1);

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


ОБНОВЛЕНИЕ

Из вашего последнего обновления StringBuilder не беспокоит, и проблема связана с ResultSet. Я чувствую, что название вопроса и все данные ответы не соответствуют вашей текущей озабоченности.
Я предлагаю закрыть этот вопрос и открыть новый для новой озабоченности :-)

person KLE    schedule 02.02.2010
comment
Я не имел в виду, что полностью заменил StringBuilder, а только объединил некоторые добавления (я удалил это примечание и предоставил обновление). Однако я посмотрю на метавызовы. - person Jeff Dalley; 02.02.2010

Я сильно сомневаюсь, что StringBuilder является вашим узким местом по сравнению со временем, затрачиваемым на доступ и извлечение данных из базы данных. Тот факт, что оптимизация простой конкатенации строк существенно не изменила время выполнения, подтверждает это.

Вам нужно оптимизировать доступ к базе данных — более быстрое подключение к БД, сжатое подключение и т. д.

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

person Sean Owen    schedule 02.02.2010

На самом деле чтение информации из результирующего набора с помощью getColumnName(), next() и getValue() часто занимает гораздо больше времени, чем получение результата в первую очередь. Это особенно верно для непрокручиваемых наборов результатов.

StringBuilder выделяет память в геометрической прогрессии (newSize = FACTOR*oldSize), поэтому чем больше вы делаете, тем меньше нужно перераспределять.

Чтобы действительно проверить это, просто замените rset и rsmeta фиктивным объектом с теми же методами: пусть next() возвращает true для реалистичного количества строк, а другой методы возвращают строки реалистичной длины.

person Emmef    schedule 02.02.2010
comment
На самом деле, next() и getXXXXX() связаны с fetch; возможно, вы имеете в виду выполнить. То есть шаг выборки (эти методы) может занять больше времени, чем подготовка и выполнение запроса. Тем не менее, StringBuilder здесь не проблема, как вы говорите. - person Kevin Brock; 04.02.2010

Проблема в том, что ResultSet имеет постоянную ссылку на базу данных и получает дополнительную информацию при вызове. Могу ли я предложить вам изучить CachedRowSet (javadoc здесь). Он немедленно извлечет все данные и в противном случае будет действовать точно так же, как ResultSet. Затем вы можете закрыть соединение с базой данных, а затем начать синтаксический анализ ваших данных. Я бы посоветовал попробовать это и посмотреть, ускорит ли это ваш процесс.

person Poindexter    schedule 02.02.2010
comment
Это здорово знать; на практике производительность оставалась примерно такой же, как и в любом другом случае - она ​​должна снижать результаты, и я твердо уверен, что на данный момент узкое место почти на 100% связано с базой данных. - person Jeff Dalley; 04.02.2010

Проверьте, как вы возвращаете набор результатов. В операторе есть методы, которые позволяют вам изменить способ получения данных ResultSet.

В частности, посмотрите на

person deterb    schedule 04.02.2010

Я согласен с тем, что StringBuilder не может быть приемником в реальном времени. Однако StringBuffer может быть немного ограничен, потому что в конечном итоге ему приходится выделять дополнительное пространство по мере роста буфера (и превышения его размера по умолчанию). Это предложение касается вашего вопроса, но опять же, я сомневаюсь, что они решат вашу проблему.

Удачи

person Todd R    schedule 02.02.2010
comment
Решение этой проблемы состоит в том, чтобы предварительно выделить достаточно места или почти достаточно места, используя new StringBuilder(<initial-capacity>). - person Kevin Brock; 04.02.2010

Попробуйте заменить различные вызовы готовыми данными. Например, кто-то предположил, что виноват доступ к метаданным. Попробуйте заменить:

columnName = rsmeta.getColumnName(i+1);

с:

columnName = "Column" + i;

где i - это целое число, которое вы устанавливаете на 0 перед циклом и увеличиваете в цикле.

person Doug in Seattle    schedule 02.02.2010

Вчера у меня была аналогичная проблема: запрос к серверу Microsoft SQL 10 примерно 1,7 миллиона записей и чтение значений заняло около 50 минут. После смены; около 80 секунд...

Моя проблема заключалась в том, что использовался драйвер jdbc: единственное изменение заключалось в переходе от драйвера com.microsoft.sqlserver.jdbc версии 1.2 к драйверу net.sourceforge.jtds версии 1.2.2...

person runec    schedule 20.10.2010