2010-11-12 16 views
20

La mia azienda riceve ogni mese un set di file CSV completi di informazioni sul conto bancario che devo importare in un database. Alcuni di questi file possono essere abbastanza grandi. Ad esempio, uno è di circa 33 MB e circa 65.000 linee.Best practice per l'importazione di file CSV di grandi dimensioni

In questo momento ho un'app symfony/Doctrine (PHP) che legge questi file CSV e li importa in un database. Il mio database ha circa 35 diverse tabelle e sul processo di importazione, prendo queste righe, le divido nei loro oggetti costitutivi e li inserisco nel database. Funziona tutto magnificamente, tranne che è lento (ogni riga impiega circa un quarto di secondo) e utilizza molta memoria.

L'utilizzo della memoria è così grave che devo dividere i miei file CSV. Un file di 20.000 righe riesce a malapena a entrare. Nel momento in cui si avvicina alla fine, ho raggiunto il 95% di utilizzo della memoria. L'importazione di 65.000 file di linea non è semplicemente possibile.

Ho trovato symfony un framework eccezionale per la creazione di applicazioni e di solito non prenderei in considerazione l'idea di utilizzare altro, ma in questo caso sono disposto a buttare tutti i miei preconcetti fuori dalla finestra in nome della performance. Non sono impegnato in alcuna lingua specifica, DBMS o altro.

Stack Overflow non piace domande soggettive Quindi ho intenzione di provare a fare questo come non-soggettivo possibile: per quelli di voi hanno non solo un parere, ma esperienza l'importazione di file di grandi dimensioni CSV, quali strumenti/pratiche hai utilizzato in passato che hanno avuto successo?

Ad esempio, usi semplicemente l'ORM/OOP di Django e non hai avuto problemi? Oppure leggi l'intero file CSV in memoria e prepari alcune dichiarazioni gigantesche di INSERT?

Ancora una volta, non voglio solo un'opinione, ma qualcosa che in realtà ha funzionato per te in passato.

Modifica: non sto solo importando un foglio di calcolo CSV a 85 colonne in una tabella di database a 85 colonne. Sto normalizzando i dati e inserendolo in dozzine di tabelle diverse. Per questo motivo, non posso semplicemente usare LOAD DATA INFILE (sto usando MySQL) o qualsiasi altra funzionalità di DBMS che legge solo nei file CSV.

Inoltre, non è possibile utilizzare alcuna soluzione specifica Microsoft.

+0

hai eseguito un'analisi delle prestazioni sul terminale DB in termini di modalità di creazione/commit delle transazioni? –

+0

No. La mia intera importazione è racchiusa in un'unica grande transazione. Per quanto riguarda le singole affermazioni "INSERT", non ho fatto alcuna analisi delle prestazioni. Qualsiasi consiglio ci sarebbe apprezzato. (Tuttavia, questo da solo non risolve i miei problemi di utilizzo della memoria.) –

risposta

10

Ho avuto lo stesso identico problema circa 2 settimane fa. Ho scritto alcuni .NET per fare inserimenti ROW BY ROW e con i miei calcoli con la quantità di dati che avevo, ci sarebbe voluta circa una settimana per farlo in questo modo.

Quindi ho usato un generatore di stringhe per creare una query ENORME e l'ho inviata al mio sistema relazionale tutto in una volta. Passò da prendere una settimana a prendere 5 minuti. Ora non so quale sistema relazionale stai utilizzando, ma con query enormi probabilmente dovrai modificare il tuo parametro max_allowed_packet o simile.

+0

@ Kmarks2: sembra una soluzione interessante ma dai un'occhiata alla mia soluzione a questa risposta - sebbene non rilevante per Jason, potrebbe esserti davvero di aiuto - Bulk Insert è estremamente veloce e se sei tu usando NET, hai il pieno controllo su quali dati sono inseriti (cioè non deve provenire da un file) –

+0

Interessante. Quante righe sono state inserite da ciascuna istruzione 'INSERT'? (Sono su MySQL, btw.) –

+1

@Jason c'erano circa 1,5 milioni. – kmarks2

1

Se si utilizza Sql Server e si ha accesso a .NET, è possibile scrivere un'applicazione rapida per utilizzare la classe SQLBulkCopy. Ho usato questo nei progetti precedenti per ottenere molti dati in SQL molto rapidamente. La classe SQLBulkCopy fa uso del BCP di SQL Server, quindi se stai usando qualcosa di diverso da .NET, potrebbe essere utile verificare se l'opzione è aperta anche a te. Non sono sicuro se stai utilizzando un DB diverso da SQL Server.

16

Perdonami se non capisco correttamente il problema, ma sembra che tu stia solo cercando di ottenere una grande quantità di dati CSV in un database SQL. C'è qualche motivo per cui vuoi utilizzare un'app Web o un altro codice per elaborare i dati CSV in istruzioni INSERT? Ho avuto successo importando grandi quantità di dati CSV in SQL Server Express (versione gratuita) utilizzando SQL Server Management Studio e utilizzando le istruzioni BULK INSERT. Un semplice inserimento di massa sarebbe il seguente:

BULK INSERT [Company].[Transactions] 
    FROM "C:\Bank Files\TransactionLog.csv" 
    WITH 
    (
     FIELDTERMINATOR = '|', 
     ROWTERMINATOR = '\n', 
     MAXERRORS = 0, 
     DATAFILETYPE = 'widechar', 
     KEEPIDENTITY 
    ) 
GO 
+0

+1 Risposta piacevole. Anche questo usa BCP (come fa la mia risposta) ma il tuo non richiede la codifica. @Jason: se un file popola più tabelle (penso che lo faccia) allora BCP in una singola tabella e utilizza istruzioni batch SQL per eseguire la suddivisione in tabelle pertinenti - dovrebbe essere ancora più veloce della tua attuale soluzione –

+1

Il motivo è perché sono non solo importando un foglio di calcolo CSV a 85 colonne in una tabella di database a 85 colonne. Sto normalizzando i dati e inserendolo in tabelle diverse. –

+1

Jason: Grazie per l'aggiornamento, cambia un po 'le cose, ma le risposte effettive potrebbero ancora essere valide. È possibile utilizzare il metodo più rapido disponibile per ottenere dati in MySQL e quindi eseguire la normalizzazione/suddivisione in MySQL come istruzioni batch. –

1

che non mi piacciono alcune delle altre risposte :)

che ho usato per fare questo in un lavoro.

Si scrive un programma per creare un grande script SQL completo di istruzioni INSERT, uno per riga. Di come esegui la sceneggiatura. È possibile salvare lo script per riferimento futuro (registro economico). Usa gzip e ridurrà le dimensioni del 90%.

Non hai bisogno di strumenti di fantasia e non importa quale database stai usando.

È possibile eseguire alcune centinaia di inserti per transazione o tutti in un'unica transazione, dipende da voi.

Python è un buon linguaggio per questo, ma sono sicuro che anche php va bene.

In caso di problemi di prestazioni alcuni database come Oracle hanno uno speciale programma di caricamento di massa che è più veloce delle istruzioni INSERT.

Si dovrebbe esaurire la memoria perché si dovrebbe analizzare solo una linea alla volta. Non hai bisogno di tenere tutto in memoria, non farlo!

+0

Pure Genius, risolto il mio problema. Versione semplificata: non importa ora, crea il file sql e importa in seguito (preferibilmente con uno strumento di importazione sql come http://www.mysqldumper.net/ per gestire l'effettiva grande importazione) Converti e poi importa. – iGNEOS

0

Sto leggendo un file CSV che ha quasi record 1M e 65 colonne. Ogni 1000 record elaborati in PHP, c'è una grossa dichiarazione MySQL che va nel database. La scrittura non richiede affatto tempo. È l'analisi che fa. La memoria utilizzata per elaborare questo file 600 MB non compresso è di circa 12 MB.

0

Ho bisogno di fare anche questo di tanto in tanto (importare grandi CSV non standardizzati in cui ogni riga crea una dozzina di oggetti DB correlati) quindi ho scritto uno script python dove posso specificare cosa va dove e come è tutto relazionato. Lo script quindi genera semplicemente istruzioni INSERT.

Eccolo: csv2db

Disclaimer: io sono fondamentalmente un noob quando si tratta di banche dati, quindi ci potrebbe essere modi migliori per ottenere questo risultato.

4

Primo: 33 MB è non grande. MySQL può facilmente gestire i dati di queste dimensioni.

Come notato, l'inserimento riga per riga è lento. L'uso di un ORM è ancora più lento: c'è un sovraccarico per la creazione di oggetti, la serializzazione e così via. L'utilizzo di un ORM per eseguire questa operazione su 35 tabelle è ancora più lento. Non farlo.

Si può effettivamente utilizzare LOAD DATA INFILE; basta scrivere uno script che trasforma i dati nel formato desiderato, separandolo in file per tabella nel processo. È quindi possibile LOAD ogni file nella tabella corretta. Questo script può essere scritto in qualsiasi lingua.

A parte questo, anche il bulk INSERT (column, ...) VALUES ... funziona.Non indovinare quale dovrebbe essere la dimensione del lotto della tua riga; tempo empiricamente, come la dimensione ottimale dei lotti dipenderà dalla vostra particolare configurazione del database (configurazione del server, tipi di colonna, indici, ecc)

Bulk INSERT non sta per essere veloce come LOAD DATA INFILE, e ti Devo ancora scrivere uno script per trasformare i dati grezzi in query utilizzabili INSERT. Per questo motivo, probabilmente farei LOAD DATA INFILE se possibile.

2

È possibile utilizzare Mysql LOAD DATA INFILE statemnt, che consente di leggere i dati da un file di testo e importare i dati del file in una tabella di database molto veloce ..

LOAD DATA INFILE '/opt/lampp/htdocs/sample.csv' INTO TABLE discounts FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 ROWS (title,@expired_date,discount) SET expired_date = STR_TO_DATE(@expired_date, '%m/%d/%Y');

per ulteriori informazioni: http://dev.mysql.com/doc/refman/5.5/en/load-data.html e http://www.mysqltutorial.org/import-csv-file-mysql-table/

4

FWIW le seguenti operazioni hanno causato un enorme aumento di velocità della mia LOAD DATA INFILE:

SET FOREIGN_KEY_CHECKS = 0; 
SET UNIQUE_CHECKS = 0; 
SET SESSION tx_isolation='READ-UNCOMMITTED'; 
SET sql_log_bin = 0; 
#LOAD DATA LOCAL INFILE.... 
SET UNIQUE_CHECKS = 1; 
SET FOREIGN_KEY_CHECKS = 1; 
SET SESSION tx_isolation='READ-REPEATABLE'; 

Vedere articolo here

+0

Ciò ha richiesto l'inserimento dei dati di carico per 18 milioni di righe da 20 minuti a 11. Molto utile! –

0

È possibile utilizzare il generatore per file di memoria efficienti. Il piccolo frammento di seguito potrebbe aiutarti.

#Method 
public function getFileRecords($params) 
{ 
    $fp = fopen('../' . $params['file'] . '.csv', 'r'); 
    //$header = fgetcsv($fp, 1000, ','); // skip header 

    while (($line = fgetcsv($fp, 1000, ',')) != FALSE) { 
     $line = array_map(function($str) { 
      return str_replace('\N', '', $str); 
     }, $line); 

     yield $line; 
    } 

    fclose($fp); 

    return; 
} 

#Implementation 
foreach ($yourModel->getFileRecords($params) as $row) { 
    // you get row as an assoc array; 
    $yourModel->save($row); 
} 
Problemi correlati