将数据帧从 R 插入到 SQL 的有效方法
Efficient way to insert data frame from R to SQL
我有一个包含 1000 万行和 5 列的数据框,我想将其插入现有的 sql table。请注意,我没有创建 table 的权限,我只能将值插入现有的 table。我目前正在使用 RODBCext
query_ch <- "insert into [blah].[dbo].[blahblah]
(col1, col2, col3, col4, col5)
values (?,?,?,?,?)"
sqlExecute(channel, query_ch, my_data)
这花费的时间太长了(超过 10 小时)。有没有办法更快地完成此操作?
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
statement,即加载一个之前写好的CSV文件在mysql
:
LOAD DATA INFILE 'the_dump.csv' INTO TABLE test
我在这里使用 RMySQL
,但任何其他 mysql 驱动程序应该会导致类似的结果。 SQL table 实例化为:
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.
中找到良好的性能提示
我有一个包含 1000 万行和 5 列的数据框,我想将其插入现有的 sql table。请注意,我没有创建 table 的权限,我只能将值插入现有的 table。我目前正在使用 RODBCext
query_ch <- "insert into [blah].[dbo].[blahblah]
(col1, col2, col3, col4, col5)
values (?,?,?,?,?)"
sqlExecute(channel, query_ch, my_data)
这花费的时间太长了(超过 10 小时)。有没有办法更快地完成此操作?
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
statement,即加载一个之前写好的CSV文件在mysql
:LOAD DATA INFILE 'the_dump.csv' INTO TABLE test
我在这里使用 RMySQL
,但任何其他 mysql 驱动程序应该会导致类似的结果。 SQL table 实例化为:
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.