将数据框从 R 插入 SQL 的有效方法

Posted

技术标签:

【中文标题】将数据框从 R 插入 SQL 的有效方法【英文标题】:Efficient way to insert data frame from R to SQL 【发布时间】:2017-05-10 00:14:42 【问题描述】:

我有一个包含 1000 万行和 5 列的数据框,我想插入到现有的 sql 表中。请注意,我没有创建表的权限,我只能将值插入到现有表中。我目前正在使用 RODBCext

query_ch <- "insert into [blah].[dbo].[blahblah] 
               (col1, col2, col3, col4, col5)
               values (?,?,?,?,?)"

sqlExecute(channel, query_ch, my_data) 

这需要的时间太长(超过 10 小时)。有没有办法更快地做到这一点?

【问题讨论】:

这不是 R 特定的问题:通过许多单个 mysql 语句导入数据实在是太慢了。如果您可以使用,最快的方法可能是将数据写入 csv 文件并使用命令行工具 mysqlimportLOAD DATA INFILE 语法。另一种提高速度的策略是在导入数据之前锁定表...... 【参考方案1】:

TL;DR:LOAD DATA INFILE 比多个 INSERT 语句快一个数量级,而后者本身比单个 INSERT 语句快一个数量级。

我将以下三种主要策略从 R 导入 Mysql 进行基准测试:

    单个insert 语句,如问题:

    INSERT INTO test (col1,col2,col3) VALUES (1,2,3)

    多个insert 语句,格式如下:

    INSERT INTO test (col1,col2,col3) VALUES (1,2,3),(4,5,6),(7,8,9)

    load data infile声明,即在mysql中加载之前写入的CSV文件:

    LOAD DATA INFILE 'the_dump.csv' INTO TABLE test


我在这里使用RMySQL,但任何其他mysql驱动程序都应该导致类似的结果。 SQL 表被实例化为:

CREATE TABLE `test` (
  `col1` double, `col2` double, `col3` double, `col4` double, `col5` double
) ENGINE=MyISAM;

连接和测试数据是在R 中创建的:

library(RMySQL)
con = dbConnect(MySQL(),
                user = 'the_user',
                password = 'the_password',
                host = '127.0.0.1',
                dbname='test')

n_rows = 1000000 # number of tuples
n_cols = 5 # number of fields
dump = matrix(runif(n_rows*n_cols), ncol=n_cols, nrow=n_rows)
colnames(dump) = paste0('col',1:n_cols)

对单个 insert 语句进行基准测试:

before = Sys.time()
for (i in 1:nrow(dump)) 
  query = paste0('INSERT INTO test (',paste0(colnames(dump),collapse = ','),') VALUES (',paste0(dump[i,],collapse = ','),');')
  dbExecute(con, query)

time_naive = Sys.time() - before 

=> 在我的计算机上这大约需要 4 分钟


对多个 insert 语句进行基准测试:

before = Sys.time()
chunksize = 10000 # arbitrary chunk size
for (i in 1:ceiling(nrow(dump)/chunksize)) 
  query = paste0('INSERT INTO test (',paste0(colnames(dump),collapse = ','),') VALUES ')
  vals = NULL
  for (j in 1:chunksize) 
    k = (i-1)*chunksize+j
    if (k <= nrow(dump)) 
      vals[j] = paste0('(', paste0(dump[k,],collapse = ','), ')')
    
  
  query = paste0(query, paste0(vals,collapse=','))
  dbExecute(con, query)

time_chunked = Sys.time() - before 

=> 这在我的计算机上大约需要 40 秒


基准测试load data infile 声明

before = Sys.time()
write.table(dump, 'the_dump.csv',
          row.names = F, col.names=F, sep='\t')
query = "LOAD DATA INFILE 'the_dump.csv' INTO TABLE test"
dbSendStatement(con, query)
time_infile = Sys.time() - before 

=> 这在我的计算机上大约需要 4 秒


设计您的 SQL 查询来处理许多插入值是提高性能的最简单方法。转换到LOAD DATA INFILE 将带来最佳结果。可以在this page of mysql documentation 中找到良好的性能提示。

【讨论】:

以上是关于将数据框从 R 插入 SQL 的有效方法的主要内容,如果未能解决你的问题,请参考以下文章

如何一次性将完整的 R 数据框插入 SQL 表

如何在一个脚本中使用 R 将 SQL 查询中的数据插入到单独的 SQL 查询中?

将行从一个表插入到另一个表中,哪个 sql 更有效(外连接 vs 顺序扫描)

如何将单行 R data.frame 插入 SQL Server 数据库?

在R中的几列中获取月度均值的有效方法

如何通过 SQL Server 插入的索引有效地替换长字符串?