2010-11-12 10 views
20

Mein Unternehmen erhält jeden Monat eine Reihe von CSV-Dateien mit Bankkontodaten, die ich in eine Datenbank importieren muss. Einige dieser Dateien können ziemlich groß sein. Zum Beispiel ist einer etwa 33 MB und etwa 65.000 Zeilen.Best Practices für den Import großer CSV-Dateien

Im Moment habe ich eine Symfony/Doctrine App (PHP), die diese CSV-Dateien liest und in eine Datenbank importiert. Meine Datenbank hat ungefähr 35 verschiedene Tabellen, und während des Imports nehme ich diese Zeilen, spalte sie in ihre konstituierenden Objekte und füge sie in die Datenbank ein. Es funktioniert alles wunderbar, außer es ist langsam (jede Zeile dauert etwa eine viertel Sekunde) und es verwendet viel Speicher.

Der Speicherverbrauch ist so schlecht, dass ich meine CSV-Dateien aufteilen muss. Eine Datei mit 20.000 Zeilen macht es kaum fertig. Wenn es fast zu Ende ist, habe ich eine Speicherauslastung von etwa 95%. Das Importieren dieser 65.000 Zeilen Datei ist einfach nicht möglich.

Ich habe festgestellt, dass Symfony ein außergewöhnlicher Rahmen für das Erstellen von Anwendungen ist, und normalerweise würde ich nichts anderes mehr verwenden, aber in diesem Fall bin ich bereit, alle meine Vorurteile im Namen der Leistung aus dem Fenster zu werfen. Ich bin keiner bestimmten Sprache, DBMS oder irgendetwas verpflichtet.

Stack-Überlauf nicht wie subjektive Fragen so dass ich versuchen werde dies als un-subjektiv wie möglich zu machen: für die von Ihnen haben nicht nur eine Meinung, aber Erfahrung importiert große CSV-Dateien, welche Werkzeuge/Praktiken haben Sie in der Vergangenheit verwendet, die erfolgreich gewesen sind?

Verwenden Sie zum Beispiel nur Djangos ORM/OOP und Sie hatten keine Probleme? Oder lesen Sie die gesamte CSV-Datei in den Speicher und bereiten Sie einige gigantische INSERT Aussagen vor?

Wieder möchte ich nicht nur eine Meinung, sondern etwas, das in der Vergangenheit tatsächlich für Sie gearbeitet hat.

Bearbeiten: Ich importiere nicht nur eine 85-Spalten-CSV-Tabelle in eine 85-Spalten-Datenbanktabelle. Ich normalisiere die Daten und setze sie in Dutzende verschiedener Tabellen ein. Aus diesem Grund kann ich nicht einfach LOAD DATA INFILE (ich benutze MySQL) oder irgendeine andere DBMS-Funktion verwenden, die nur CSV-Dateien einliest.

Außerdem kann ich keine Microsoft-spezifischen Lösungen verwenden.

+0

Haben Sie am Ende der DB eine Leistungsanalyse durchgeführt, in Bezug darauf, wie die Transaktionen erstellt/festgelegt werden? –

+0

Nein. Mein gesamter Import ist in einer großen Transaktion verpackt. Soweit die einzelnen INSERT-Anweisungen selbst gehen, habe ich keine Performance-Analyse durchgeführt. Jeder Rat dort würde geschätzt werden. (Das allein löst jedoch nicht meine Speicherprobleme.) –

Antwort

10

Ich hatte genau dieses Problem vor etwa 2 Wochen. Ich habe einige .NET geschrieben, um ROW-by-ROW-Einfügungen zu machen, und nach meinen Berechnungen mit der Menge an Daten, die ich hatte, würde es ungefähr eine Woche dauern, bis es so war.

Also habe ich stattdessen einen String-Builder verwendet, um eine HUGE-Abfrage zu erstellen und sie auf einmal an mein relationales System zu senden. Es dauerte von einer Woche bis zu 5 Minuten. Jetzt weiß ich nicht, welches relationale System Sie verwenden, aber bei enormen Abfragen müssen Sie wahrscheinlich Ihren max_allowed_packet-Parameter oder ähnliches optimieren.

+0

@ Kmarks2: klingt eine interessante Lösung, aber werfen Sie einen Blick auf meine Lösung für diese Antwort - für Jason nicht relevant, es hat Ihnen wirklich geholfen haben - Bulk Insert ist extrem schnell und wenn Sie ' Wenn Sie .NET verwenden, haben Sie die volle Kontrolle darüber, welche Daten eingefügt werden (dh sie müssen nicht aus einer Datei stammen). –

+0

Interessant. Wie viele Zeilen hat jede Ihrer INSERT-Anweisungen eingefügt? (Ich bin auf MySQL, übrigens.) –

+1

@Jason gab es rund 1,5 Millionen. – kmarks2

1

Wenn Sie Sql Server verwenden und Zugriff auf .NET haben, können Sie eine schnelle Anwendung schreiben, um die Klasse SQLBulkCopy zu verwenden. Ich habe dies in früheren Projekten verwendet, um sehr schnell viele Daten in SQL zu bekommen. Die SQLBulkCopy-Klasse verwendet die BCP von SQL Server. Wenn Sie also etwas anderes als .NET verwenden, lohnt es sich, zu prüfen, ob diese Option auch für Sie offen ist. Nicht sicher, ob Sie eine andere Datenbank als SQL Server verwenden.

16

Verzeihen Sie, wenn ich Ihr Problem nicht genau verstehe, aber es scheint, als ob Sie nur versuchen, eine große Menge an CSV-Daten in eine SQL-Datenbank zu bekommen. Gibt es einen Grund, warum Sie eine Web-App oder einen anderen Code verwenden möchten, um die CSV-Daten in INSERT-Anweisungen zu verarbeiten? Ich habe erfolgreich große Mengen von CSV-Daten in SQL Server Express (kostenlose Version) mit SQL Server Management Studio importiert und BULK INSERT-Anweisungen verwendet. Eine einfache Masseneinfügung würde wie folgt aussehen:

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

+1 Schöne Antwort. Dies verwendet auch BCP (wie meine Antwort), aber Ihre erfordert keine Codierung. @Jason: Wenn eine Datei mehrere Tabellen auffüllt (ich denke, es tut) dann BCP in eine einzige Tabelle und verwenden Sie SQL-Batch-Anweisungen, um die Aufspaltung in relevanten Tabellen - sollte immer noch schneller als Ihre aktuelle Lösung sein –

+1

Der Grund ist, weil ich bin Importieren Sie nicht nur eine 85-Spalten-CSV-Tabelle in eine 85-Spalten-Datenbanktabelle. Ich normalisiere die Daten und lege sie in verschiedene Tabellen. –

+1

Jason: Danke für das Update, es ändert die Dinge ein wenig, aber die tatsächlichen Antworten könnten noch gültig sein. Sie könnten die schnellere verfügbare Methode verwenden, um Daten in MySQL zu bekommen, und dann die Normalisierung/Aufspaltung in MySQL als Batch-Anweisungen durchführen. –

1

ich einige der anderen Antworten nicht :) mag

habe ich dies zu einem Job zu tun.

Sie schreiben ein Programm, um ein großes SQL-Skript mit INSERT-Anweisungen zu erstellen, eins pro Zeile. Dann führst du das Skript aus. Sie können das Skript zur späteren Referenz speichern (billiges Protokoll). Benutze gzip und es verkleinert die Größe um 90%.

Sie benötigen keine ausgefallenen Werkzeuge und es ist wirklich egal, welche Datenbank Sie verwenden.

Sie können ein paar hundert Einsätze pro Transaktion oder alle in einer Transaktion tun, es liegt an Ihnen.

Python ist eine gute Sprache dafür, aber ich bin sicher php ist auch in Ordnung.

Wenn Sie Leistungsprobleme haben, haben einige Datenbanken wie Oracle ein spezielles Massenladeprogramm, das schneller als INSERT-Anweisungen ist.

Sie sollten nicht genügend Arbeitsspeicher haben, da Sie nur eine Zeile nach der anderen analysieren sollten. Du musst das Ganze nicht in Erinnerung behalten, tu das nicht!

+0

Pure Genius, löste mein Problem. Simplere Version: Dont Import Nun, Create SQL-Datei und importieren Sie später (vorzugsweise mit einem SQL-Import-Tool wie http://www.mysqldumper.net/, um den eigentlichen großen Import zu behandeln) Convert und dann importieren. – iGNEOS

0

Ich lese eine CSV-Datei, die fast 1M Datensätze und 65 Spalten hat. Je 1000 Datensätze in PHP verarbeitet werden, gibt es eine große fette MySQL-Anweisung, die in die Datenbank eingeht. Das Schreiben braucht keine Zeit. Es ist das Parsing. Der Arbeitsspeicher für die Verarbeitung dieser unkomprimierten 600-MB-Datei beträgt ca. 12 MB.

0

Ich muss das auch von Zeit zu Zeit tun (importiere große, nicht-standardisierte CSVs, wo jede Zeile ein Dutzend verwandter DB-Objekte erzeugt), also habe ich ein Python-Skript geschrieben, wo ich angeben kann, wohin und wie alles geht verbunden. Das Skript generiert dann einfach INSERT-Anweisungen.

Hier ist sie: csv2db

Disclaimer: Ich bin im Grunde ein noob, wenn es um Datenbanken kommt, so könnte es bessere Wege, dies zu erreichen.

4

Erstens: 33MB ist nicht groß. MySQL kann problemlos Daten dieser Größe verarbeiten.

Wie Sie bemerkt haben, ist die Zeileneinfügung langsam. Die Verwendung eines ORM darüber hinaus ist noch langsamer: Es gibt einen Overhead für das Erstellen von Objekten, die Serialisierung und so weiter. Mit einem ORM, um dies über 35 Tabellen zu tun ist noch langsamer. Tu das nicht.

Sie können tatsächlich LOAD DATA INFILE; Schreiben Sie einfach ein Skript, das Ihre Daten in das gewünschte Format umwandelt und dabei in einzelne Dateien aufteilt. Sie können dann jede Datei in die richtige Tabelle LOAD. Dieses Skript kann in jeder Sprache geschrieben werden.

Abgesehen davon funktioniert Bulk INSERT (column, ...) VALUES ... auch.Raten Sie nicht, wie groß Ihre Zeilengröße sein sollte. Zeit empirisch, als die optimale Losgröße auf Ihrem bestimmten Datenbank-Setup abhängig (Serverkonfiguration, Spaltentypen, Indizes, etc.)

Masse INSERT wird nicht so schnell wie LOAD DATA INFILE, und Sie werden muss noch ein Skript schreiben, um Rohdaten in brauchbare INSERT Abfragen umzuwandeln. Aus diesem Grund würde ich wahrscheinlich LOAD DATA INFILE wenn möglich tun.

2

können Sie Mysql verwenden LOAD DATA INFILE statemnt ermöglicht es Ihnen, Daten aus einer Textdatei zu lesen und die Daten der Datei in einer Datenbanktabelle importieren sehr schnell ..

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');

für weitere Informationen: http://dev.mysql.com/doc/refman/5.5/en/load-data.html und http://www.mysqltutorial.org/import-csv-file-mysql-table/

4

FWIW verursacht die folgenden Schritte eine große Beschleunigung meiner 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'; 

Siehe Artikel here

+0

Dies nahm meine Ladedaten einfügen für 18 Millionen Zeilen von 20 Minuten bis 11. Super hilfreich! –

0

Sie können Generator für Speicher effiziente Datei bereit verwenden. Der kleine Ausschnitt unten könnte Ihnen helfen.

#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); 
}