TL;DR:
LOAD DATA INFILE
é uma ordem de magnitude mais rápida que vários INSERT
instruções, que são uma ordem de magnitude mais rápidas do que um único INSERT
declarações. Comparo abaixo as três principais estratégias para importar dados do R para o Mysql:
-
únicoinsert
declarações , como na pergunta:
INSERT INTO test (col1,col2,col3) VALUES (1,2,3)
-
váriosinsert
declarações , formatado assim:
INSERT INTO test (col1,col2,col3) VALUES (1,2,3),(4,5,6),(7,8,9)
-
load data infile
declaração , ou seja, carregar um arquivo CSV escrito anteriormente emmysql
:
LOAD DATA INFILE 'the_dump.csv' INTO TABLE test
Eu uso
RMySQL
aqui, mas qualquer outro driver mysql deve levar a resultados semelhantes. A tabela SQL foi instanciada com:CREATE TABLE `test` (
`col1` double, `col2` double, `col3` double, `col4` double, `col5` double
) ENGINE=MyISAM;
Os dados de conexão e teste foram criados em
R
com: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)
Benchmarking único
insert
declarações: 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
=> isso leva cerca de 4 minutos no meu computador
Comparando com vários
insert
declarações: 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
=> isso leva cerca de 40 segundos no meu computador
Benchmarking
load data infile
declaração :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
=> isso leva cerca de 4 segundos no meu computador
Criar sua consulta SQL para lidar com muitos valores de inserção é a maneira mais simples de melhorar o desempenho. Transição para
LOAD DATA INFILE
levará a ótimos resultados. Dicas de bom desempenho podem ser encontradas esta página de documentação do mysql .