将一个非常大的文件解析成mysql

时间:2013-02-06 10:19:59

标签: php mysql parsing unix

我有一个任务,我需要解析一个非常大的文件,并将结果写入一个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 个答案:

答案 0 :(得分:2)

第一个优化是插入一个事务 - 每个100或1000行提交并开始一个新事务。显然,您必须使用支持事务的存储引擎。

然后使用top命令观察CPU使用情况 - 如果你有多个内核,mysql进程没有做太多工作,PHP进程完成大部分工作,重写脚本以接受跳过n的参数从一开始的行,只导入10000行左右。然后启动脚本的多个实例,每个实例都有不同的起点。

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

答案 1 :(得分: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);

答案 2 :(得分:0)

我发现您的描述相当混乱 - 而且与您提供的代码不符。

  

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

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

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

答案 3 :(得分:0)

我首先会使用该脚本来创建一个SQL文件。然后使用此http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html锁定表,方法是在SQL文件的开头/结尾放置相应的命令(可以让脚本执行此操作)。

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