web-dev-qa-db-ja.com

タイムアウトとメモリエラーなしで非常に大きなcsvファイルを処理する

現時点では、非常に大きなCSVファイルのインポートスクリプトを作成しています。問題は、タイムアウトのためにしばらくしてから停止するか、メモリエラーをスローする場合がほとんどです。

私のアイデアは、CSVファイルを「100行」の手順で解析し、100行後にスクリプトを自動的に呼び出すことでした。私はヘッダーでこれを達成しようとしました(場所...)、getで現在の行を渡しましたが、それは私が望むようにはうまくいきませんでした。

これに良い方法はありますか、誰かがメモリエラーとタイムアウトを取り除く方法を知っていますか?

24
Julian

fgetcsv を使用して、120MBのcsvをストリーム単位で読み取りました(正しい英語ですか?)。それは行ごとに読み取り、その後、データベースにすべての行を挿入しました。そのようにして、各反復でメモリに保持されるのは1行だけです。スクリプトにはまだ20分かかりました。走る。たぶん私はPython次回…巨大なcsvファイルを配列にロードしようとしないでください。それは本当に多くのメモリを消費します。

// WDI_GDF_Data.csv (120.4MB) are the World Bank collection of development indicators:
// http://data.worldbank.org/data-catalog/world-development-indicators
if(($handle = fopen('WDI_GDF_Data.csv', 'r')) !== false)
{
    // get the first row, which contains the column-titles (if necessary)
    $header = fgetcsv($handle);

    // loop through the file line-by-line
    while(($data = fgetcsv($handle)) !== false)
    {
        // resort/rewrite data and insert into DB here
        // try to use conditions sparingly here, as those will cause slow-performance

        // I don't know if this is really necessary, but it couldn't harm;
        // see also: http://php.net/manual/en/features.gc.php
        unset($data);
    }
    fclose($handle);
}
51
feeela

ファイルをアップロードし、mysqlのLOAD DATA LOCALクエリを使用して挿入すると、高速なソリューションが見つかります。例:

    $sql = "LOAD DATA LOCAL INFILE '/path/to/file.csv' 
        REPLACE INTO TABLE table_name FIELDS TERMINATED BY ',' 
        ENCLOSED BY '\"' LINES TERMINATED BY '\r\n' IGNORE 1 LINES";
    $result = $mysqli->query($sql);
16
Craigo

所要時間と必要なメモリ量を気にしない場合は、このスクリプトの値を単純に増やすことができます。スクリプトの先頭に次の行を追加するだけです。

ini_set('memory_limit', '512M');
ini_set('max_execution_time', '180');

関数 memory_get_usage() を使用すると、スクリプトがmemory_limitの適切な値を見つけるために必要なメモリ量を確認できます。

fgets() を見て、ファイルを1行ずつ読み取ることもできます。それがより少ないメモリを必要とするかどうかはわかりませんが、これはうまくいくと本当に思います。ただし、この場合でも、max_execution_timeをより高い値に増やす必要があります。

13
2ndkauboy

メモリ消費に関しては、fgetcsv()とfgets()には大きな違いがあるようです。 1列のみの単純なCSVは、fgetcsv()を使用して50000レコードの512Mのメモリ制限を超え、それを報告するのに8分かかりました。

Fgets()を使用すると、649175レコードを正常に処理するのに3分しかかからず、ローカルサーバーは追加の空気を切らしていませんでした。

したがって、csvの列数が制限されている場合は、fgets()を使用することをお勧めします。私の場合、fgets()は列1内の文字列を直接返しました。複数の列については、各レコード操作の後にunset()する使い捨て配列でexplode()を使用できます。回答3を親指しました@ndkauboy

1
rawdesk.be