2010-09-23 3 views
2

Пару лет назад я написал небольшую утилиту для перемещения данных из Oracle db в db Postgres. Я использовал Java и JDBC для этого, потому что я хотел, чтобы Java обрабатывала форматирование данных для данных, используемых в подготовленном заявлении для вставки. Исходная версия утилиты предполагала, что имена таблиц и имена столбцов были одинаковыми в обеих базах данных. Более поздние версии приняли файл сопоставления для обработки различий имен. Эта утилита была большим хитом в моей организации, но, к сожалению, она не масштабировалась. Он максимизируется примерно на миллион строк, перемещаемых в час. Теперь у нас есть таблицы с 30 + миллионами строк, и никто не хочет ждать 30 часов для передачи своих данных.Oracle to Postgres data transfer

Метод ниже - это сердце полезности и причина, по которой он не масштабируется. Этот метод выполняется один раз для каждого столбца данных, поэтому он вызывается (num_rows * num_cols раз). С профилировщиком я вижу, что этот метод потребляет 58% времени выполнения. Только вызовы getObject() и findColumn() составляют 53% времени выполнения!

public void setPlaceholderValue (int placeHolderNum, ResultSet rs, String oracleColumnName, PreparedStatement stmt) throws Exception { 

    int columnIndex = rs.findColumn(oracleColumnName) ; 
    int columnType = rs.getMetaData().getColumnType(columnIndex) ; 

    try{ 
     if (rs.getObject(oracleColumnName) != null){ 
      switch (columnType) { 
       case Types.VARCHAR: stmt.setString(placeHolderNum, rs.getString(columnIndex)); break; 
       case Types.INTEGER: stmt.setInt(placeHolderNum, rs.getInt(columnIndex)); break ; 
       case Types.DATE:  stmt.setDate(placeHolderNum, rs.getDate(columnIndex)); break; 
       case Types.FLOAT:  stmt.setFloat(placeHolderNum, rs.getFloat(columnIndex)); break ; 
       case Types.NUMERIC: stmt.setBigDecimal(placeHolderNum,rs.getBigDecimal(columnIndex)); break ; 
       case Types.TIMESTAMP:  stmt.setTimestamp(placeHolderNum, rs.getTimestamp(columnIndex)); break ; 
       default: throw new SQLException("The result set column type " + rs.getMetaData().getColumnType(columnIndex) + " was not recognized. see the java.sql.Types class at http://java.sun.com/j2se/1.5.0/docs/api/ "); 
      } 
     } else { 
      stmt.setNull(placeHolderNum, columnType); 
     } 
    } catch (SQLException e){ 
     System.out.println ("SQLException: " + e.getMessage() + " for record id=" + rs.getLong("id")); 
     throw new SQLException("rethrow"); 
    } 
} 

Я не уверен, что смогу реорганизовать этот метод, чтобы снизить время передачи. Я думаю, что столбец под столбцом просто не масштабируется.

Может ли кто-нибудь предложить лучший способ сделать это? Язык не проблема, я могу сделать это с чем-нибудь, что может справиться с работой . В идеале я хотел бы видеть скорость передачи не менее 10 миллионов записей в час.

+0

10M записи час кажется довольно smokin '. –

ответ

5

Я бы предложил использовать предоставленные DB инструменты экспорта/импорта. Oracle и PostgreSQL поддерживают как форматы XML, так и CSV.

Если вы хотите придерживаться JDBC, а затем поместить столбцы в SELECT запросе на ResultSet в том же порядке, что и значение в INSERT запросе на PreparedStatement и просто следующий вместо этого всего блока if/switch :

preparedStatement.setObject(index, resultSet.getObject(index)); 

Однако я не ожидаю, что это значительно улучшит производительность. Возможности экспорта/импорта, предоставляемые DB, могут сделать это намного эффективнее, чем когда-либо на Java.

+1

Вторичный. Лучший способ выполнить это (IMHO) - это написать запросы к таблицам Oracle, которые отображают данные в таблицах PostgreSQL и запускают их для создания файлов CSV. Затем используйте команду PostgreSQL COPY для импорта данных. 10M строк в час ничего нет - я регулярно импортирую 28M в одну таблицу, строят индексы и получаю две совокупные таблицы всего за час в PostgreSQL. –

+0

Мне придется исследовать это. –

0

Возможно, вы попытаетесь создать класс какого-либо типа (возможно, массив некоторого разнообразия) для хранения информации о столбцах и типах набора результатов, которые являются инвариантными при обработке заданного набора результатов. Затем вытащите значения из массива вместо того, чтобы делать вызовы findColumn и getColumnType каждый раз, когда они вам понадобятся. Это должно значительно сократить вызовы findColumn и getColumnType и помочь улучшить время выполнения.

Удачи.

1

Эта линия может быть проблематичным:

if (rs.getObject(oracleColumnName) != null){ 

Вместо того, чтобы использовать:

if (rs.getObject(columnIndex) != null){ 

В Oracle GetObject (String) представляет собой О (п) - по крайней мере, на 10 г это было. Этот метод выглядит так, что он вызывается для каждого столбца для каждой строки в наборе результатов. Вы не должны получать метаданные для каждого вызова. Переместите все вызовы, относящиеся к метаданным, которые будут выполняться один раз для каждого запроса и передайте их этому методу при перемещении по набору результатов.

0

Информация, которую вы получаете, является постоянной для всей обработки этой таблицы.

Чтобы выполнить это только один раз в таблице, вы можете создать держатель данных, содержащий элементы для имени столбца, индекса столбца, типа столбца и флага hasColumn. Передайте экземпляр этого объекта данных вместо имени columnName, инициализируйте его в первый раз и используйте данные для остальной части таблицы. Такие данные, как это, будут сэкономить 2*num_rows*num_cols вызовы для извлечения метаданных.