Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Maneira eficiente de inserir data frame de R para SQL


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:

  1. único insert declarações , como na pergunta:

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

  2. vários insert declarações , formatado assim:

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

  3. load data infile declaração , ou seja, carregar um arquivo CSV escrito anteriormente em mysql :

    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 .