【问题标题】:RODBC insertion performance(very slow) in R into ORACLE Database using sqlQuery使用 sqlQuery 将 R 中的 RODBC 插入性能(非常慢)插入 ORACLE 数据库
【发布时间】:2020-04-02 09:36:11
【问题描述】:

我们正在尝试使用 RODBC 连接和 sqlQuery 函数将数据帧插入 Oracle 数据库。我们也在使用mclapply函数来实现并行。插入性能非常慢,因为插入 360 万条记录需要 6 个小时。数据库端没有创建日志。

请告诉我们如何提高 R 中的插入性能,如果需要更多信息,请告诉我们。

Target1Conn<-odbcConnect('TARGET_DATABASE','USERNAME','PASSWORD')
if(nrow(InputData)>0)
{
     sqls<-sprintf(paste0('insert into ', 
         outputTableName,'(col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col12,col13,col14,col15) 
         values(%s)'), 
         apply(InputData, 1, function(i) paste(i, collapse=",")))
 }
 mclapply(sqls, function(s) {
     sqlQuery(Target1Conn, s)
 },mc.cores=4)

【问题讨论】:

  • 您是否尝试过sqlSave 而不是循环遍历行:sqlSave(Target1Conn, InputData, outputTableName, fast=TRUE, append=TRUE, rownames=FALSE)
  • 嗨 Parfait,我们已经尝试过 sqlSave,但它正在创建表默认数据类型,而不是指定数据类型。更重要的是,加载数据需要同样的时间。
  • 你能试试遵循 R 的 DBI 标准(包括 RJDBC、ROracle、RMySQL、RSQLite)的odbc 包并使用dbWriteTable吗?
  • 我们正面临 odbc 包的一些问题并试图解决它。问题解决后,我们将尝试使用 odbc 实现并测试性能。

标签: r database oracle performance rodbc


【解决方案1】:

在 Oracle 中,与著名博客强调的单个插入选择相比,使用 INSERT...VALUES 追加数百万条记录是次优的:

dba-oracle.com:

不要使用标准 SQL 插入 - 它们比其他方法慢得多。

asktom.oracle.com:

最快的方法是禁用索引(将它们标记为不可用)并在单次插入中执行此操作

stackoverflow.com@Lokesh:

选择插入是最快的方法,因为所有内容都保留在 RAM 中。


因此,请考虑使用 RODBC 的 sqlSave 或 DBI 的 dbWriteTable 构建临时临时表。并帮助对齐变量类型。请参阅@Linda 的this SO answer

RODBC::sqlSave(Target1Conn, InputData, "myTempTable", fast=TRUE, append=FALSE, rownames=FALSE)

DBI::dbWriteTable(Target1Conn, InputData, "myTempTable", append=FALSE, row.names=FALSE)

然后,运行 single 插入选择(并尝试使用 Oracle 的 APPEND 提示,如下所示):

sql <- "INSERT /*+ append */ INTO myFinalTable (col1, col2, col3, col4, col5,
                                  col6, col7, col8, col9, col10,
                                  col11, col12, col13, col14, col15)
        SELECT col1, col2, col3, col4, col5,
               col6, col7, col8, col9, col10,
               col11, col12, col13, col14, col15
        FROM myTempTable"


RODBC::sqlQuery(Target1Conn, sql)

DBI::dbExecute(Target1Conn, sql)

进一步的优化技术。请务必在继续之前与您的 DBA 讨论。

  • 在仍在使用上述临时表的存储过程中使用了BULK COLLECT(如果可用)。

    Oracle (只运行一次)

    CREATE OR REPLACE PROCEDURE load_data
    IS
    TYPE TObjectTable IS TABLE OF ALL_OBJECTS%ROWTYPE;
    ObjectTable$ TObjectTable;
    
    BEGIN
      SELECT * BULK COLLECT INTO ObjectTable$
        FROM myTempTable;
    
      FORALL x in ObjectTable$.First..ObjectTable$.Last
       INSERT INTO myFinalTable VALUES ObjectTable$(x) ;
    END;
    

    R

    RODBC::sqlQuery(Target1Conn, "EXEC load_data")
    DBI::dbExecute(Target1Conn, "EXEC load_data")
    
  • 让 R 导出一个 csv 或其他分隔的文本文件

    utils::write.csv(InputData, "/output/path/inputdata.csv")
    # data.table::fwrite(InputData, "/output/path/inputdata.csv")  # ALTERNATIVE
    

    然后使用 Oracle 的 SQL*Loader,它非常棒,专为批量插入而设计,正如 @Ben 所建议的那样。

    1. 下面另存为.ctl文本文件,如myload.ctl

      OPTIONS (SKIP=1)
      LOAD DATA 
        INFILE "/output/path/inputdata.csv"
        INTO TABLE "myschema"."myFinalTable"
        FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' 
        (col1 INTEGER, 
         col2 DATE 'YYYY-MM-DDD', 
         col3 VARCHAR(50),
         col4 NUMBER(10,2),
         col5 CHAR(255),
         ...)
      
    2. 运行sqlldr命令行(在同一目录输出错误和问题)

      sqlldr CONTROL=myload.ctl, LOG=myload.log, BAD=myload.bad USERID=user/pwd
      

    当然,R 可以使用writeLines() 自动创建上述.ctl,并在命令行中使用system() 调用sqlldr

  • 暂时禁用索引,使用上述临时表加载新数据,然后重新启用索引。见procedure hereSQL*LOADER 也可能需要这样做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-12
    • 1970-01-01
    • 1970-01-01
    • 2011-03-09
    相关资源
    最近更新 更多