2009-06-30 7 views
6

Devo inserire circa 1,8 milioni di righe da un file CSV in un database MySQL. (solo una tabella)MySQL Inserimento di grandi set di dati da file con Java

Attualmente si utilizza Java per analizzare il file e inserire ogni riga.

Come potete immaginare, ci vogliono molte ore per essere eseguiti. (10 in modo razionale)

Il motivo per cui non lo inserisco direttamente nel file nel db, è che i dati devono essere manipolati prima di essere aggiunto al database.

Questo processo deve essere eseguito da un responsabile IT. Così l'ho configurato come un bel file batch da far girare dopo che hanno rilasciato il nuovo file csv nella giusta posizione. Quindi, ho bisogno di farlo funzionare bene, inserendo il file in una determinata posizione ed eseguendo un file batch. (Ambiente Windows)

La mia domanda è, quale sarebbe il modo più veloce per inserire tanti dati; inserti di grandi dimensioni, da un file analizzato temporaneo o un inserto alla volta? qualche altra idea forse?

La seconda domanda è: come ottimizzare la mia installazione MySQL per consentire inserimenti molto veloci. (Vi sarà un punto in cui una grande selezionato di tutti i dati è richiesto pure)

Nota: la tabella verrà eventualmente droped e l'intero processo eseguito nuovamente in un secondo momento.

Alcuni chiarimenti: attualmente in uso ... opencsv.CSVReader per analizzare il file, quindi facendo un inserimento su ogni riga. Sto considerando alcune colonne e ignorando gli altri.

Altro chiarimento: locale DB tabella MyISAM

+3

Non inserire per ogni riga, lotti fino un sacco di file e rendere meno DB chiama, la velocità salirà drammaticamente. Vedere la mia risposta per un semplice esempio di batch PreparedStatement. – Hardwareguy

risposta

14

Suggerimenti per l'inserimento veloce:

  • utilizzare la sintassi LOAD DATA INFILE di lasciare MySQL analizzarlo e inserirla, anche se si dispone di storpiare e alimentarlo dopo la manipolazione.
  • Utilizzare questa sintassi inserto:

    inserto nella tabella (col1, col2) valori (val1, val2), (val3, val4), ...

  • rimuovere tutte le chiavi/indici prima dell'inserimento .

  • Fallo nella macchina più veloce che hai (principalmente per l'IO, ma anche la RAM e la CPU).Sia il server DB, ma anche il client di inserimento, ricorda che pagherai due volte il prezzo di I/O (una volta letto, il secondo inserendo)
+0

Avere il file sul server è di gran lunga il più veloce, ma se non si dispone di quel tipo di accesso, è comunque possibile utilizzare LOAD DATA INFILE LOCALE. Assicurati di utilizzare una connessione compressa se è un file di grandi dimensioni. –

+1

grazie, in realtà alla fine ho caricato i dati direttamente come stava usando il file locale dei dati di caricamento. Ho quindi scritto una serie di query sql piuttosto complesse per creare un'altra tabella temporanea nel formato che volevo. Il tempo totale è ora ridotto a 30 secondi per 1,8 milioni di record. Non male dalle 10 ore originali create dallo sviluppatore originale. Tutto questo fatto in mysql, non è richiesto java. –

+3

Fantastico! L'opzione 2 (insert syntax) mi ha ridotto da 77 minuti a 26 secondi quando si inserivano 400.000 righe. –

1

Si dovrebbe usare LOAD DATA sulla console MySQL in sé per questo e non funziona attraverso il codice ...

LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table; 

se è necessario manipolare i dati, consiglio comunque di manipolare in memoria, riscrivere su un file flat e spingerlo nel database usando LOAD DATA, penso che dovrebbe essere più efficiente.

+0

-1 ha detto che aveva bisogno di manipolare i dati prima di inserirli nel DB – Hardwareguy

+0

@Hardwareguy: si prega di vedere il cambiamento che ho aggiunto (prima di vederti commentare :) –

+0

Toglierò il mio meno uno ma continuo a non pensare questo è il modo migliore. – Hardwareguy

0

Non sarebbe più veloce se si usasse LOAD DATA INFILE invece di inserire ogni riga?

+0

-1. Citazione dalla domanda: "La ragione per cui non sto collegando direttamente dal file al db, è che i dati devono essere manipolati prima di aggiungerlo al database" – PatrikAkerstrand

+0

Ho visto questo: manipolare i dati, salvarli in un file temporaneo, chiama "carica dati infile", elimina il file temporaneo. – Pierre

4

Probabilmente selezionerei un numero elevato, come 10k righe, e caricherò che molte righe dal CSV, massaggiano i dati e fanno un aggiornamento in batch, quindi ripetono fino a quando non hai completato l'intero CSV. A seconda del numero di massaggi/quantità di dati, le file da 1,8 mil non dovrebbero richiedere 10 ore, più di 1-2 ore a seconda dell'hardware.

edit: whoops, lasciato fuori una parte abbastanza importante, il vostro deve avere con autocommit impostato su false, il codice ho copiato questo da stava facendo come parte del metodo GetConnection().

Connection con = GetConnection(); 
con.setAutoCommit(false); 
      try{ 
       PreparedStatement ps = con.prepareStatement("INSERT INTO table(col1, col2) VALUES(?, ?)"); 
       try{ 
        for(Data d : massagedData){ 
         ps.setString(1, d.whatever()); 
             ps.setString(2, d.whatever2()); 
              ps.addBatch(); 
        } 
        ps.executeBatch(); 
       }finally{ 
        ps.close(); 
       } 
      }finally{ 
       con.close(); 
      } 
+1

1-2 ore è ancora lento come l'inferno.LOAD FROM INFILE termina in pochi secondi se è nello stesso formato della tabella, specialmente se il file .csv risiede già sul server. Provalo, è incredibilmente veloce. In genere non si desidera un set di dati incompleto nel database, quindi sarà necessario utilizzare una transazione e bloccare le tabelle. Personalmente non conosco alcun server di produzione in cui le tabelle di blocco per 1-2 ore siano accettabili. –

+0

Ha detto 1,8 mil mil. Anche questo è un tavolo temporaneo quindi non bloccherà altri tavoli. – Hardwareguy

1

Un'altra idea: si usa un PreparedStatement per l'inserimento dei dati con JDBC?

+0

PreparedStatements con addBatch sono il modo in cui lo faccio sempre. – Hardwareguy

+0

interessante, questo offre miglioramenti delle prestazioni? attualmente usando .... opencsv.CSVReader per analizzare il file, quindi facendo un inserimento su ogni riga. Sto considerando alcune colonne e ignorando gli altri. –

+1

C'è un sovraccarico nel fare semplicemente una connessione al database. Vedrai un'enorme accelerazione inserendo gli inserti. – Hardwareguy

0

Correvo tre fili ...

1) legge il file di input e spinge ciascuna riga in una coda di trasformazione 2) Pops dalla coda, trasforma i dati, e spinge in una coda db 3) Pops dalla coda db e inserisce i dati

In questo modo, si può essere la lettura dei dati dal disco, mentre i fili db stanno aspettando il loro IO per completare e viceversa

+1

Questo suona bene in teoria, ma il thread 3 è dove il 95% del lavoro sta per accadere, quindi in realtà non si otterrà molto parallelizzando le attività thread 1 e 2. – Hardwareguy

+0

Ovviamente ciò dipende dalle trasformazioni. Nella mia esperienza che può coinvolgere molte ricerche di database per validare i campi. Se il file di origine si trova su un disco diverso da quello dei file di database, dovrebbe comunque verificarsi un aumento delle prestazioni. Se devono essere sullo stesso disco, farei di tutto per ridurlo a 1000 o più file per ridurre la ricerca della testa. – Reed

+0

Di solito vado con 2 thread (leggi & analizza + carica). Non ho lavorato su reti e server superiori (nel qual caso il terzo thread potrebbe essere utile) ma in genere le operazioni di caricamento e di database richiedono molto più tempo rispetto alla semplice lettura di un file e all'analisi di un paio di interi. – Pijusn

0

Se non sei già , prova ad usare il tipo di tabella MyISAM, assicurati solo di leggere le sue carenze prima di yo lo fai In genere è più veloce rispetto agli altri tipi di tabelle.

Se la tabella ha gli indici, di solito è più veloce per farli cadere quindi aggiungere di nuovo dopo l'importazione.

Se i dati sono tutte stringhe, ma è più adatto come database relazionale, sarà meglio inserire numeri interi che indicano altri valori anziché memorizzare una stringa lunga.

Ma in generale, sì l'aggiunta di dati in un database richiede tempo.

1

A seconda di cosa esattamente dovete fare con i dati prima di inserirla le opzioni migliori in termini di velocità sono:

  • analizzare il file in java/fai quello che ti serve con i dati/scrivere la "massaggiati" trasferire i dati in un nuovo file CSV/utilizzare "caricare dati in infile" su quello.
  • Se la manipolazione dei dati è condizionata (per esempio è necessario verificare la presenza di record di esistenza e fare cose diverse a seconda che si tratta di un inserimento o di aggiornamento e, ecc ...), allora (1) può essere impossibile. In tal caso, è meglio fare inserimenti/aggiornamenti in batch.
    Esperimento per trovare il miglior lavoro dimensione del lotto per voi (a partire con circa 500-1000 dovrebbe essere ok). A seconda del motore di archiviazione che si sta utilizzando per la vostra tavola, potrebbe essere necessario dividere questo in più operazioni, nonché - avere un unico file 1.8M una campata non è andare a fare miracoli per le prestazioni.
  • 1

    Il tuo più grande problema di prestazioni è molto probabilmente non java ma mysql, in particolare gli indici, i vincoli e le chiavi esterne che hai sul tavolo si sta inserendo in. Prima di iniziare i tuoi inserti, assicurati di disabilitarli. Riattivarli alla fine richiederà molto tempo, ma è molto più efficiente che avere il database che li valuta dopo ogni dichiarazione.

    Si può anche essere vedendo i problemi di prestazioni mysql a causa delle dimensioni della transazione.Il tuo log delle transazioni diventerà molto grande con molti inserimenti, quindi eseguire un commit dopo il numero X di inserti (ad esempio 10.000-100.000) aiuterà anche a inserire la velocità.

    Dal livello jdbc, assicurarsi di utilizzare i comandi addBatch() ed executeBatch() piuttosto che su PreparedStatement anziché sul normale executeUpdate().

    2

    Sei assolutamente CERTO che hai disabilitato i commit automatici nel driver JDBC?

    Questo è il tipico killer delle prestazioni per i client JDBC.

    +0

    Io non sono ... lo controllerò ... grazie. –

    1

    È possibile migliorare le prestazioni inserimento di massa da MySQL/Java utilizzando la capacità di dosaggio nel suo driver JDBC connettore J.

    MySQL non gestisce "correttamente" i batch (vedere il mio articolo link, in basso), ma può riscrivere INSERT per utilizzare la sintassi MySQL ecc., Ad es. si può dire al conducente di riscrivere due inserti:

    INSERT INTO (val1, val2) VALUES ('val1', 'val2'); 
    INSERT INTO (val1, val2) VALUES ('val3', 'val4'); 
    

    come una singola istruzione:

    INSERT INTO (val1, val2) VALUES ('val1', 'val2'), ('val3','val4'); 
    

    (Si noti che non sto dicendo si necessità di riscrivere il vostro SQL in questo modo, il driver lo fa quando è possibile)

    Abbiamo fatto questo per una nostra ricerca di inserto di massa: ha fatto un ordine di grandezza della differenza. Utilizzato con transazioni esplicite come menzionato da altri e vedrai un grande miglioramento complessivo.

    L'impostazione della proprietà del driver di riferimento è:

    jdbc:mysql:///<dbname>?rewriteBatchedStatements=true 
    

    See: A 10x Performance Increase for Batch INSERTs With MySQL Connector/J Is On The Way

    Problemi correlati