web-dev-qa-db-ja.com

大きなCSVファイルをインポートするためのベストプラクティス

私の会社では、毎月、データベースにインポートする必要がある銀行口座情報が含まれた一連のCSVファイルを取得しています。これらのファイルのいくつかはかなり大きくなる可能性があります。たとえば、1つは約33MBで約65,000行です。

現在、これらのCSVファイルを読み取ってデータベースにインポートするsymfony/Doctrineアプリ(PHP)があります。私のデータベースには約35の異なるテーブルがあり、インポートの過程で、これらの行を取得し、それらを構成オブジェクトに分割して、データベースに挿入します。 slow(各行は約4分の1秒かかります)であり、大量のメモリを使用することを除いて、すべてが美しく機能します。

メモリの使用状況が非常に悪いため、CSVファイルを分割する必要があります。 20,000行のファイルではほとんどうまくいきません。終わりに近づく頃には、メモリ使用率は95%に達しています。その65,000行のファイルをインポートすることは不可能です。

Symfonyはアプリケーションを構築するための例外的なフレームワークであることがわかり、通常は他の何も使用することは考えませんが、この場合、パフォーマンスの名の下に、私のすべての先入観をウィンドウから放棄します。私は特定の言語、DBMS、または何かにコミットしていません。

スタックオーバーフローは主観的な質問を好まないので、これを可能な限り主体性のないものにしようと思います。意見だけではなく、大きなCSVファイルのインポートの経験、どのようなツールがあるのか/ practices have 過去に使用されました成功しましたか?

たとえば、DjangoのORM/OOPだけを使用していて、問題は発生していませんか?または、CSVファイル全体をメモリに読み込んで、いくつかの巨大なINSERTステートメントを準備しますか?

繰り返しますが、私は意見だけでなく、過去に実際に役立ったものも欲しいです。

編集:85列のCSVスプレッドシートを85列のデータベーステーブルにインポートするだけではありません。私はデータを正規化して、それを数十の異なるテーブルに入れています。このため、LOAD DATA INFILE(MySQLを使用しています)や、CSVファイルを読み取るだけの他のDBMSの機能を使用することはできません。

また、マイクロソフト固有のソリューションは使用できません。

24
Jason Swett

私は約2週間前にまったく同じ問題を抱えていました。 ROW BY ROW挿入を行うために.NETをいくつか作成しましたが、持っていたデータ量を使った計算では、この方法でこれを行うのに1週間ほどかかります。

そのため、代わりに文字列ビルダーを使用して1つの巨大なクエリを作成し、それをリレーショナルシステムに一度に送信しました。 1週間から5分かかりました。現在、使用しているリレーショナルシステムはわかりませんが、膨大なクエリでは、おそらくmax_allowed_pa​​cketパラメータなどを微調整する必要があります。

11
kmarks2

問題を正確に理解していない場合はご容赦ください。ただし、大量のCSVデータをSQLデータベースに取得しようとしているようです。 CSVデータをINSERTステートメントに処理するためにWebアプリまたはその他のコードを使用する理由はありますか? SQL Server Management StudioとBULK INSERTステートメントを使用して、大量のCSVデータをSQL Server Express(無料バージョン)にインポートすることに成功しました。単純な一括挿入は次のようになります。

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

最初:33MBはnot大きいです。 MySQLはこのサイズのデータ​​を簡単に処理できます。

お気づきのとおり、行ごとの挿入は低速です。その上でORMを使用するとさらに遅くなります。オブジェクトの構築、シリアル化などのオーバーヘッドが発生します。 ORMを使用して35個のテーブルにわたってこれを行うと、さらに遅くなります。これを行わないでください。

確かに_LOAD DATA INFILE_を使用できます。データを目的の形式に変換するスクリプトを記述し、その過程でデータをテーブルごとのファイルに分離します。次に、各ファイルを適切なテーブルにLOADできます。このスクリプトは任意の言語で記述できます。

それとは別に、バルクINSERT (column, ...) VALUES ...も機能します。行のバッチサイズが何であるかを推測しないでください。 最適なバッチサイズは特定のデータベース設定(サーバー構成、列タイプ、インデックスなど)に依存するため、経験的に時間

一括INSERTは_LOAD DATA INFILE_ほど高速ではないため、未加工のデータを使用可能なINSERTクエリに変換するスクリプトを作成する必要があります。このため、可能であれば_LOAD DATA INFILE_を実行します。

5
candu

FWIW次の手順により、私の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';

記事を見る ここ

4
Sam

他のいくつかの答えは好きではありません:)

私は仕事でこれをやっていました。

1行に1つずつ、INSERTステートメントでいっぱいの大きなSQLスクリプトを作成するプログラムを作成します。スクリプトを実行するよりも。後で参照するためにスクリプトを保存できます(安価なログ)。 gzipを使用すると、サイズが90%ほど縮小されます。

高度なツールは必要ありません。実際にどのデータベースを使用していてもかまいません。

トランザクションごとに数百の挿入を実行することも、1つのトランザクションでそれらすべてを実行することもできます。

Pythonはこれに適した言語ですが、phpも問題ないと思います。

パフォーマンスの問題がある場合、Oracleなどの一部のデータベースには、INSERTステートメントよりも高速な特別なバルクロードプログラムがあります。

一度に1行しか解析しないため、メモリが不足するはずです。すべてをメモリに保持する必要はありません。それをしないでください!

2
Glen P

Mysql _LOAD DATA INFILE_ statemntを使用できます。これにより、テキストファイルからデータを読み取り、ファイルのデータをデータベーステーブルに非常に高速にインポートできます。

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

詳細情報: http://dev.mysql.com/doc/refman/5.5/en/load-data.html および http://www.mysqltutorial.org/import -csv-file-mysql-table /

2
R T

ジェネレータを使用して、メモリ効率の高いファイルを準備できます。以下の小さなスニペットが役立つかもしれません。

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

Sql Serverを使用していて、.NETにアクセスできる場合は、 SQLBulkCopy クラスを使用する簡単なアプリケーションを作成できます。以前のプロジェクトでこれを使用して、大量のデータをすばやくSQLに取り込みました。 SQLBulkCopyクラスはSQL ServerのBCPを利用するので、.NET以外のものを使用している場合は、そのオプションがあなたに開かれているかどうかを調べる価値があるかもしれません。 SQL Server以外のDBを使用しているかどうかはわかりません。

1
Paul Hadfield

100万件近くのレコードと65列のCSVファイルを読んでいます。 PHPで処理される1000レコードごとに、データベースに入る大きな太いMySQLステートメントが1つあります。書くのに全く時間はかかりません。行うのは解析です。この非圧縮600MBファイルの処理に使用されるメモリは約12 MBです。

0
Cyril Joudieh

私も時々これを行う必要があります(各行が1ダース程度の関連するDBオブジェクトを作成する大きな非標準化CSVをインポートします)。pythonスクリプトを書いて、どこにどのように行くのかを指定できるようにしました関連。その後、スクリプトは単にINSERTステートメントを生成します。

ここにあります: csv2db

免責事項:私はデータベースに関しては基本的に初心者なので、これを達成するためのより良い方法があるかもしれません。

0
Lukas