Jdbc Oracle批量更新某些表的性能不佳

时间:2012-03-03 22:16:23

标签: performance oracle jdbc insert batch-processing

嘿,我真的需要帮助,我很沮丧。

我正在研究这个项目,即将数据从一个数据库迁移到另一个数据库,这两个都是Oracle。我正在做的是从源数据库中获取表并在目标数据库中创建表。然后从源表中获取数据以批量插入到目标数据库中。

提供更多细节;

我通过从源表获取16个rowid范围并将数据复制的选择查询分发到8个服务器,每个2个并行执行此操作。 (实际上在测试服务器上我分配到4台机器,每台4台)。然后我在每个线程中选择要插入表格的数据。

让主模块在一台服务器上工作,该服务器在目标上创建表并决定rowid范围,以将任务发送到在其他机器上工作的从模块。但对于某些表来说,它确实很慢。我不知道为什么。现在我有两张桌子,一张桌子很快就完成了。而另一个则非常缓慢。从相同的db和相同的表空间和数据文件,到另一个db,相同的表空间和相同的数据文件。

我可以快速复制13936.4375 MB的大小,107.910.833行,并在5分钟内结束。这是表格的ddl

    -- Create table
create table CMP_SUBS_RESPONSE_INS
(
  RESPONSE_TP_SK            NUMBER(16),
  CHURN_REASON_TP_SK        NUMBER(16),
  CONTRACT_SK               NUMBER(16),
  OFFER_SK                  NUMBER(16),
  CHANNEL_SK                NUMBER(16),
  CMP_RUN_SK                NUMBER(16),
  CAMPAIGN_CELLS_SK         NUMBER(16),
  SUBS_RESPONSE_SK          NUMBER(16),
  SUBS_RESPONSE_NK          VARCHAR2(50),
  SUBS_RESPONSE_NK2         NUMBER,
  CRC_CALCULATION_FLAG      VARCHAR2(2),
  SOURCE_SYSTEM_SK          NUMBER(16),
  INITIAL_ETL_DATE          DATE,
  MODIFICATION_SYSTIME      DATE,
  UPDATE_ETL_DATE           DATE,
  START_DATE                DATE,
  END_DATE                  DATE,
  FULFILLMENT_FLAG          VARCHAR2(1),
  CHURN_SUBREASON_TP_SK     NUMBER(16),
  LMC_REASON_CATEGORY_SK    NUMBER(16),
  LMC_REASON_TIMEPERIOD_SK  NUMBER(16),
  LMC_REASON_PAYMENTTYPE_SK NUMBER(16)
)
tablespace USERS_BTS
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );    

---target

create table BR_TEST2
(
  RESPONSE_TP_SK            NUMBER(16),
  CHURN_REASON_TP_SK        NUMBER(16),
  CONTRACT_SK               NUMBER(16),
  OFFER_SK                  NUMBER(16),
  CHANNEL_SK                NUMBER(16),
  CMP_RUN_SK                NUMBER(16),
  CAMPAIGN_CELLS_SK         NUMBER(16),
  SUBS_RESPONSE_SK          NUMBER(16),
  SUBS_RESPONSE_NK          VARCHAR2(50),
  SUBS_RESPONSE_NK2         NUMBER,
  CRC_CALCULATION_FLAG      VARCHAR2(2),
  SOURCE_SYSTEM_SK          NUMBER(16),
  INITIAL_ETL_DATE          DATE,
  MODIFICATION_SYSTIME      DATE,
  UPDATE_ETL_DATE           DATE,
  START_DATE                DATE,
  END_DATE                  DATE,
  FULFILLMENT_FLAG          VARCHAR2(1),
  CHURN_SUBREASON_TP_SK     NUMBER(16),
  LMC_REASON_CATEGORY_SK    NUMBER(16),
  LMC_REASON_TIMEPERIOD_SK  NUMBER(16),
  LMC_REASON_PAYMENTTYPE_SK NUMBER(16)
)
tablespace PUB_A_BTS
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );

花费太多时间完成的那个是11519 MB大小,大约3700万行,并且需要超过2-3小时或更多(实际上我总是感到沮丧并且杀死了这个过程,从来没有看到了结束)

create table DGG1
(
  YEAR_MONTH             VARCHAR2(250),
  SUBSCRIBER_ID          NUMBER,
  EXT_DATE               TIMESTAMP(6),
  CALC_MONTHS            VARCHAR2(250),
  LAST_3_MONTH_FLAG      VARCHAR2(250),
  AVEA1_YTL              NUMBER,
  AVEA1_SAVING_YTL       NUMBER,
  AVEA1_SAVING_PER       NUMBER,
  AVEA2_YTL              NUMBER,
  AVEA2_SAVING_YTL       NUMBER,
  AVEA2_SAVING_PER       NUMBER,
  AVEA3_YTL              NUMBER,
  AVEA3_SAVING_YTL       NUMBER,
  AVEA3_SAVING_PER       NUMBER,
  AVEA4_YTL              NUMBER,
  AVEA4_SAVING_YTL       NUMBER,
  AVEA4_SAVING_PER       NUMBER,
  AVEA5_YTL              NUMBER,
  AVEA5_SAVING_YTL       NUMBER,
  AVEA5_SAVING_PER       NUMBER,
  AVEA6_YTL              NUMBER,
  AVEA6_SAVING_YTL       NUMBER,
  AVEA6_SAVING_PER       NUMBER,
  AVEA7_YTL              NUMBER,
  AVEA7_SAVING_YTL       NUMBER,
  AVEA7_SAVING_PER       NUMBER,
  AVEA8_YTL              NUMBER,
  AVEA8_SAVING_YTL       NUMBER,
  AVEA8_SAVING_PER       NUMBER,
  AVEA9_YTL              NUMBER,
  AVEA9_SAVING_YTL       NUMBER,
  AVEA9_SAVING_PER       NUMBER,
  AVEA10_YTL             NUMBER,
  AVEA10_SAVING_YTL      NUMBER,
  AVEA10_SAVING_PER      NUMBER,
  TELSIM1_YTL            NUMBER,
  TELSIM1_SAVING_YTL     NUMBER,
  TELSIM1_SAVING_PER     NUMBER,
  TELSIM2_YTL            NUMBER,
  TELSIM2_SAVING_YTL     NUMBER,
  TELSIM2_SAVING_PER     NUMBER,
  TELSIM3_YTL            NUMBER,
  TELSIM3_SAVING_YTL     NUMBER,
  TELSIM3_SAVING_PER     NUMBER,
  TELSIM4_YTL            NUMBER,
  TELSIM4_SAVING_YTL     NUMBER,
  TELSIM4_SAVING_PER     NUMBER,
  TELSIM5_YTL            NUMBER,
  TELSIM5_SAVING_YTL     NUMBER,
  TELSIM5_SAVING_PER     NUMBER,
  TELSIM6_YTL            NUMBER,
  TELSIM6_SAVING_YTL     NUMBER,
  TELSIM6_SAVING_PER     NUMBER,
  TELSIM7_YTL            NUMBER,
  TELSIM7_SAVING_YTL     NUMBER,
  TELSIM7_SAVING_PER     NUMBER,
  TELSIM8_YTL            NUMBER,
  TELSIM8_SAVING_YTL     NUMBER,
  TELSIM8_SAVING_PER     NUMBER,
  TELSIM9_YTL            NUMBER,
  TELSIM9_SAVING_YTL     NUMBER,
  TELSIM9_SAVING_PER     NUMBER,
  TELSIM10_YTL           NUMBER,
  TELSIM10_SAVING_YTL    NUMBER,
  TELSIM10_SAVING_PER    NUMBER,
  ETT_DATE               TIMESTAMP(6),
  RUN_ID                 VARCHAR2(250),
  CO_ID                  NUMBER,
  UNIQUE_PARTY_ID        NUMBER,
  UNOPTIMISEABLE_CHARGES NUMBER,
  OTHER_CHARGES          NUMBER
)
tablespace USERS_BTS
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64k
    next 1m
    minextents 1
    maxextents unlimited
  );

----target

create table dds_etl.br_test
(
  YEAR_MONTH             VARCHAR2(250),
  SUBSCRIBER_ID          NUMBER,
  EXT_DATE               TIMESTAMP(6),
  CALC_MONTHS            VARCHAR2(250),
  LAST_3_MONTH_FLAG      VARCHAR2(250),
  AVEA1_YTL              NUMBER,
  AVEA1_SAVING_YTL       NUMBER,
  AVEA1_SAVING_PER       NUMBER,
  AVEA2_YTL              NUMBER,
  AVEA2_SAVING_YTL       NUMBER,
  AVEA2_SAVING_PER       NUMBER,
  AVEA3_YTL              NUMBER,
  AVEA3_SAVING_YTL       NUMBER,
  AVEA3_SAVING_PER       NUMBER,
  AVEA4_YTL              NUMBER,
  AVEA4_SAVING_YTL       NUMBER,
  AVEA4_SAVING_PER       NUMBER,
  AVEA5_YTL              NUMBER,
  AVEA5_SAVING_YTL       NUMBER,
  AVEA5_SAVING_PER       NUMBER,
  AVEA6_YTL              NUMBER,
  AVEA6_SAVING_YTL       NUMBER,
  AVEA6_SAVING_PER       NUMBER,
  AVEA7_YTL              NUMBER,
  AVEA7_SAVING_YTL       NUMBER,
  AVEA7_SAVING_PER       NUMBER,
  AVEA8_YTL              NUMBER,
  AVEA8_SAVING_YTL       NUMBER,
  AVEA8_SAVING_PER       NUMBER,
  AVEA9_YTL              NUMBER,
  AVEA9_SAVING_YTL       NUMBER,
  AVEA9_SAVING_PER       NUMBER,
  AVEA10_YTL             NUMBER,
  AVEA10_SAVING_YTL      NUMBER,
  AVEA10_SAVING_PER      NUMBER,
  TELSIM1_YTL            NUMBER,
  TELSIM1_SAVING_YTL     NUMBER,
  TELSIM1_SAVING_PER     NUMBER,
  TELSIM2_YTL            NUMBER,
  TELSIM2_SAVING_YTL     NUMBER,
  TELSIM2_SAVING_PER     NUMBER,
  TELSIM3_YTL            NUMBER,
  TELSIM3_SAVING_YTL     NUMBER,
  TELSIM3_SAVING_PER     NUMBER,
  TELSIM4_YTL            NUMBER,
  TELSIM4_SAVING_YTL     NUMBER,
  TELSIM4_SAVING_PER     NUMBER,
  TELSIM5_YTL            NUMBER,
  TELSIM5_SAVING_YTL     NUMBER,
  TELSIM5_SAVING_PER     NUMBER,
  TELSIM6_YTL            NUMBER,
  TELSIM6_SAVING_YTL     NUMBER,
  TELSIM6_SAVING_PER     NUMBER,
  TELSIM7_YTL            NUMBER,
  TELSIM7_SAVING_YTL     NUMBER,
  TELSIM7_SAVING_PER     NUMBER,
  TELSIM8_YTL            NUMBER,
  TELSIM8_SAVING_YTL     NUMBER,
  TELSIM8_SAVING_PER     NUMBER,
  TELSIM9_YTL            NUMBER,
  TELSIM9_SAVING_YTL     NUMBER,
  TELSIM9_SAVING_PER     NUMBER,
  TELSIM10_YTL           NUMBER,
  TELSIM10_SAVING_YTL    NUMBER,
  TELSIM10_SAVING_PER    NUMBER,
  ETT_DATE               TIMESTAMP(6),
  RUN_ID                 VARCHAR2(250),
  CO_ID                  NUMBER,
  UNIQUE_PARTY_ID        NUMBER,
  UNOPTIMISEABLE_CHARGES NUMBER,
  OTHER_CHARGES          NUMBER
)
tablespace PUB_A_BTS
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64k
    next 1m
    minextents 1
    maxextents unlimited
  );

任何表中都没有约束或索引。你甚至不能看到空检查。

现在我编写的java代码是下面的,我确实使用jvisualvm进行采样,而且似乎是缓慢的,99%的cpu时间花在executeBatch方法上,更加特别是oracle.net.ns.Packet.receive(在executeBatch的子树中的方法。对于快速的方法,它仍然是最耗时的位,但是大约45%。

我注释掉写入机制并只执行getObject方法,并在100秒内结束加载表。所以从源db读取似乎不是问题所在。

所以我认为db需要太多时间来执行批量插入。我认为当列数增加时它会变慢,我只从缓慢复制的表中选择了20列,它仍然很慢并挂在executeBatch上。然后我认为这是因为我没有对列类型使用正确的getXXX()方法,并且我将代码从getObject适当地更改为任何get方法是必要的。但它仍然太慢了。

然后我认为db需要太多时间来分配新空间。所以我创建了一个初始范围为15gb的表,以确保它在执行批处理时不会花时间分配空间。然后它再次起作用。

每当表被复制得太慢时,我看到服务器上运行从模块的CPU活动(我在下面发布的实际数据副本的代码)非常低。对于快速,大量CPU使用率的那些。

我尝试了不同的批量大小和获取大小,但没有提供太多帮助。

所以有人能告诉我这里我做错了什么吗?我用一个花哨的etl工具复制同一个表,它可以在大约10分钟内快速完成工作。

显然是关于某些特定类型的表格。但我在这里找不到什么问题。我得到了最新的jdbc驱动程序(ojdbc6),以确保它不是问题,但它仍然是相同的。

我从源db

获取查询结果
public ResultSet getQueryResultset(Connection con, String query) throws SQLException {
        OraclePreparedStatement preparedStatement = null;
        preparedStatement = con.prepareStatement(query);
        preparedStatement.setRowPrefetch(DTSConstants.FETCH_SIZE);//1000
        return preparedStatement.executeQuery();
    }

目标连接是自动提交错误

targetConnection.setAutoCommit(false);

我如何准备插入

OraclePreparedStatement preparedStatement = null;
preparedStatement =  connection.prepareStatement(insertScript);

在这里我将数据写入目标

private int write(ResultSet resultSet, OraclePreparedStatement preparedStatement, long taskID) throws SQLException,
            DTSException, MLException, ParseException {
        int statementCounter = 0;
        int rowsAffected = 0;
        int columnCount = columnNames.length;
        while (resultSet.next()) {
            setColumnsAndAddBatch(resultSet, preparedStatement, columnCount);
            statementCounter++;
            rowsAffected++;
            if (statementCounter >= DTSConstants.BATCH_SIZE) { /1000
                preparedStatement.executeBatch();
                statementCounter = 0;
                controllerUtil.performTaskSanityCheck(taskID);
            }
        }
        preparedStatement.executeBatch();
        return rowsAffected;
    }


private void setColumnsAndAddBatch(ResultSet resultSet, OraclePreparedStatement preparedStatement, int columnCount)
            throws SQLException, MLException, ParseException {
        for (int i = 0; i < columnCount; i++) {
            Object object = resultSet.getObject(i + 1);//Changed this to Object object = OracleDataHandler.getData(resultSet, i + 1, columntypes[i]);

            if (maskingLibGateway != null) {
                String columnName = columnNames[i];
                if (maskFields.containsKey(columnName) == true) { // never true for my examples, so the method never gets called
                    object = maskObject(object, columnName); 
                }
            }
            preparedStatement.setObject(i + 1, object);
        }
        preparedStatement.addBatch();
    }

使用此类来决定getXXX方法

公共类OracleDataHandler {

public static Object getData(ResultSet resultSet, int columnIndex, int columnType) throws SQLException {

    switch (columnType) {
    case Types.NUMERIC:
    case Types.DECIMAL:
        return resultSet.getBigDecimal(columnIndex);
    case Types.CHAR:
    case Types.VARCHAR:
    case Types.LONGNVARCHAR:
        return resultSet.getString(columnIndex);
    case Types.INTEGER:
        return resultSet.getInt(columnIndex);
    case Types.DATE:
        return resultSet.getDate(columnIndex);
    case Types.TIMESTAMP:
        return resultSet.getTimestamp(columnIndex);
    case Types.TIME:
        return resultSet.getTime(columnIndex);
    case Types.BIGINT:
        return resultSet.getLong(columnIndex);
    case Types.DOUBLE:
    case Types.FLOAT:
        return resultSet.getDouble(columnIndex);
    case Types.SMALLINT:
        return resultSet.getShort(columnIndex);
    case Types.TINYINT:
        return resultSet.getByte(columnIndex);
    case Types.BINARY:
    case Types.VARBINARY:
        return resultSet.getBytes(columnIndex);
    case Types.CLOB:
        return resultSet.getClob(columnIndex);
    case Types.ARRAY:
        return resultSet.getArray(columnIndex);
    case Types.BLOB:
        return resultSet.getBlob(columnIndex);
    case Types.REAL:
        return resultSet.getFloat(columnIndex);
    case Types.BIT:
    case Types.BOOLEAN:
        return resultSet.getBoolean(columnIndex);
    case Types.REF:
        return resultSet.getRef(columnIndex);
    case Types.DATALINK:
        return resultSet.getURL(columnIndex);
    case Types.LONGVARBINARY:
        return resultSet.getBinaryStream(columnIndex);
    default:
        return resultSet.getObject(columnIndex);
    }

}

}

1 个答案:

答案 0 :(得分:1)

我解决了这个问题;

我所做的就是将setObject方法更改为适合每种数据类型的JDBC setXXX方法。就像我使用get方法一样

虽然,我不太明白究竟是什么问题。为什么只有一些表而不是其他表。它不能是时间戳数据类型,因为我选择了除时间戳之外的列。

如果有人能告诉我究竟是什么问题我真的会批评它。我假设这是因为目标数据库正在对某些数据类型进行一些转换(虽然是哪一种?)或者我在java端的内存中使数据更大并通过网络发送(使用setXXX方法可能会减小每条记录的大小?)这花了太多时间?这些只是假设。 (很可能是愚蠢的。)

无论如何,我很高兴我解决了这个问题。感谢所有回复的人。