3

非常に大きなファイルを解析し、その結果をmysqlデータベースに書き込む必要があるタスクがあります。「非常に大きい」とは、1.4GBのCSVデータ、合計で約1,000万行のテキストについて話していることを意味します。

物事はそれを行うための「方法」ではなく、それを速く行う方法です。私の最初のアプローチは、速度を最適化せずにphpで実行し、完了するまで数日間実行することでした。残念ながら、現在48時間稼働しており、ファイル全体の2%しか処理していません。したがって、それはオプションではありません。

ファイル形式は次のとおりです。

A:1,2

ここで、「:」に続くコンマ区切りの数値の量は、0〜1000にすることができます。サンプルデータセットは、次のようにテーブルに配置する必要があります。

| A | 1 |
| A | 2 |

だから今、私はこのようにそれをしました:

$fh = fopen("file.txt", "r");

$line = ""; // buffer for the data
$i = 0; // line counter
$start = time(); // benchmark

while($line = fgets($fh))
{
    $i++;       
    echo "line " . $i . ": ";

    //echo $i . ": " . $line . "<br>\n";

    $line = explode(":", $line);

    if(count($line) != 2 || !is_numeric(trim($line[0])))
    {
        echo "error: source id [" .  trim($line[0]) . "]<br>\n";
        continue;
    }

    $targets = explode(",", $line[1]);

    echo "node " .  $line[0] . " has " . count($targets) . " links<br>\n";

    // insert links in link table
    foreach($targets as $target)
    {
            if(!is_numeric(trim($target)))
            {
                echo "line " . $i . " has malformed target [" . trim($target) . "]<br>\n";
                continue;
            }

            $sql = "INSERT INTO link (source_id, target_id) VALUES ('" .  trim($line[0]) . "', '" .  trim($target) . "')";
            mysql_query($sql) or die("insert failed for SQL: ". mysql_error());
        }
}

echo "<br>\n--<br>\n<br>\nseconds wasted: " . (time() - $start);

これは明らかに速度に対して最適化されていません。新たなスタートのヒントはありますか?別の言語に切り替える必要がありますか?

4

4 に答える 4

2

最初の最適化は、トランザクションを挿入することです。100行または1000行ごとにコミットして、新しいトランザクションを開始します。明らかに、トランザクションをサポートするストレージエンジンを使用する必要があります。

次に、コマンドを使用してCPU使用率を観察しますtop-複数のコアがある場合、mysqlプロセスはあまり実行せず、PHPプロセスは多くの作業を実行します。スクリプトを書き直して、最初からn行をスキップし、10000のみをインポートするパラメーターを受け入れます。行かそこら。次に、スクリプトの複数のインスタンスを開始します。各インスタンスの開始点は異なります。

3番目の解決策は、PHPを使用してファイルをCSVに変換し(INSERTをまったく使用せず、ファイルに書き込むだけ)、LOAD DATA INFILEm4t1t0のように使用することです。

于 2013-02-06T10:35:40.927 に答える
1

約束通り、添付されているので、この投稿で私が求めた解決策を見つけることができます。私はそれをベンチマークしました、そしてそれは古いものより40倍(!)速いことがわかりました:)確かに-まだ最適化の余地はたくさんあります、しかしそれは今私にとって十分に速いです:)

$db = mysqli_connect(/*...*/) or die("could not connect to database");

$fh = fopen("data", "r");

$line = "";             // buffer for the data
$i = 0;                 // line counter
$start = time();        // benchmark timer
$node_ids = array();    // all (source) node ids

mysqli_autocommit($db, false);

while($line = fgets($fh))
{
$i++;

echo "line " . $i . ": ";

$line = explode(":", $line);
$line[0] = trim($line[0]);

if(count($line) != 2 || !is_numeric($line[0]))
{
    echo "error: source node id [" .  $line[0] . "] - skipping...\n";
    continue;
}
else
{
    $node_ids[] = $line[0];
}

$targets = explode(",", $line[1]);

echo "node " .  $line[0] . " has " . count($targets) . " links\n";

// insert links in link table
foreach($targets as $target)
{
    if(!is_numeric($target))
    {
        echo "line " . $i . " has malformed target [" . trim($target) . "]\n";
        continue;
    }

    $sql = "INSERT INTO link (source_id, target_id) VALUES ('" .  $line[0] . "', '" .  trim($target) . "')";
    mysqli_query($db, $sql) or die("insert failed for SQL: ". $db::error);
}

if($i%1000 == 0)
{
    $node_ids = array_unique($node_ids);
    foreach($node_ids as $node)
    {
        $sql = "INSERT INTO node (node_id) VALUES ('" . $node . "')";
        mysqli_query($db, $sql);
    }
    $node_ids = array();

    mysqli_commit($db);
    mysqli_autocommit($db, false);
    echo "committed to database\n\n";
}
}

echo "<br>\n--<br>\n<br>\nseconds wasted: " . (time() - $start);
于 2013-02-08T08:29:32.833 に答える
0

あなたの説明はかなり紛らわしいと思います-そしてそれはあなたが提供したコードと一致しません。

if(count($ line)!= 2 ||!is_numeric(trim($ line [0])))

ここでのトリムは冗長です-空白はis_numbericの動作を変更しません。しかし、あなたは行の始まりが文字であるとどこかで言いました-したがって、これは常に失敗します。

高速化する場合は、メッセージ処理ではなく入力のストリーム処理を使用するように切り替えるか(PHP配列は非常に遅くなる可能性があります)、別の言語を使用して挿入ステートメントを複数行の挿入に集約します。

于 2013-02-06T10:40:15.497 に答える
0

まず、スクリプトを使用してSQLファイルを作成します。次に、このhttp://dev.mysql.com/doc/refman/5.0/en/lock-tables.htmlを使用して、SQLファイルの開始/終了に適切なコマンドを配置してテーブルをロックします(スクリプトで実行できます)これ)。

次に、コマンドツールを使用してSQLをデータベースに挿入します(できればデータベースが存在するマシン上で)。

于 2013-02-06T11:52:03.597 に答える