3

我有一个任务,我需要解析一个非常大的文件并将结果写入 mysql 数据库。“非常大”意味着我们正在谈论 1.4GB 的 CSV 数据,总计约 1000 万行文本。

事情不是“如何”做,而是如何快速做。我的第一种方法是在没有任何速度优化的情况下在 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线左右。然后启动脚本的多个实例,每个实例都有不同的起点。

第三种解决方案是使用 PHP 将文件转换为 CSV(根本没有 INSERT,只是写入文件)并LOAD DATA INFILE按照 m4t1t0 的建议使用。

于 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

我会首先使用脚本创建一个 SQL 文件。然后使用此http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html通过在 SQL 文件的开头/结尾放置适当的命令来锁定表(可以让您执行脚本这个)。

然后只需使用命令工具将 SQL 注入数据库(最好在数据库所在的机器上)。

于 2013-02-06T11:52:03.597 回答
0

我发现您的描述相当混乱 - 它与您提供的代码不匹配。

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

这里的修剪是多余的——空格不会改变 is_numberic 的行为。但是你已经说过,行的开头是一个字母——因此这总是会失败的。

如果您想加快速度,请切换到使用输入的流处理而不是消息处理(PHP 数组可能非常慢)或使用不同的语言并将插入语句聚合到多行插入中。

于 2013-02-06T10:40:15.497 回答