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

Php 将一个非常大的文件解析为mysql,php,mysql,parsing,unix,Php,Mysql,Parsing,Unix,我有一项任务,需要解析一个非常大的文件,并将结果写入mysql数据库。“超大”意味着我们谈论的是1.4GB的CSV数据,总计约1000万行文本 问题不在于“如何”去做,而在于如何快速去做。我的第一种方法是只在php中运行,不进行任何速度优化,然后让它运行几天,直到完成。不幸的是,它现在已经连续运行了48小时,只处理了总文件的2%。因此,这不是一个选择 文件格式如下: A:1,2 | A | 1 | | A | 2 | 其中“:”后面逗号分隔的数字的数量可以是0-1000。示例数据集必须按如下

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

问题不在于“如何”去做,而在于如何快速去做。我的第一种方法是只在php中运行,不进行任何速度优化,然后让它运行几天,直到完成。不幸的是,它现在已经连续运行了48小时,只处理了总文件的2%。因此,这不是一个选择

文件格式如下:

A:1,2
| A | 1 |
| A | 2 |
其中“:”后面逗号分隔的数字的数量可以是0-1000。示例数据集必须按如下方式放入表中:

A:1,2
| 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);
$fh=fopen(“file.txt”、“r”);
$line=”“;//数据缓冲区
$i=0;//线路计数器
$start=time();//基准
而($line=fgets($fh))
{
$i++;
回音“行”。$i.:”;
//echo$i.:“$line.”
\n”; $line=分解(“:”,$line); 如果(计数($line)!=2 | |!是数字(修剪($line[0])) { echo“错误:源id[”.trim($line[0])。“]
\n”; 继续; } $targets=explode(“,”,$line[1]); 回显“节点”。$line[0]。.has.count($targets)。“链接
\n”; //在链接表中插入链接 foreach($targets作为$target) { 如果(!是数字(修剪($target))) { echo“line”。$i。“目标[”.trim($target)。“]
\n”; 继续; } $sql=“插入链接(源id、目标id)值(“.trim($line[0])”、“.trim($target)。”)”; mysql_query($sql)或die(“sql的插入失败:.mysql_error()); } } 回声“
\n--
\n
\n浪费的秒数:”。(time()-$start);

这显然没有以任何方式优化速度。有什么新开始的提示吗?我应该切换到另一种语言吗?

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

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


第三种解决方案是使用PHP将文件转换为CSV(完全没有插入,只是写入文件),并按照m4t1t0的建议使用
加载数据填充

我发现您的描述相当混乱,而且与您提供的代码不匹配

如果(计数($line)!=2 | |!是数字(修剪($line[0]))

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


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

我将首先使用脚本创建SQL文件。然后,通过在SQL文件的开始/结束处放置适当的命令(可以让脚本执行此操作),使用此命令锁定表


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

正如承诺的那样,您将在本文中找到我想要的解决方案。我对它进行了基准测试,结果表明,它比旧版本快40倍(!) 当然-仍有很大的优化空间,但现在对我来说已经足够快了:)

$db=mysqli_connect(/*…*/)或die(“无法连接到数据库”);
$fh=fopen(“数据”,“r”);
$line=”“;//数据缓冲区
$i=0;//线路计数器
$start=time();//基准计时器
$node_id=array();//所有(源)节点ID
mysqli_自动提交($db,false);
而($line=fgets($fh))
{
$i++;
回音“行”。$i.:”;
$line=分解(“:”,$line);
$line[0]=修剪($line[0]);
如果(计数($line)!=2 | |!是数字($line[0]))
{
echo“错误:源节点id[”$line[0]。“]-正在跳过…\n”;
继续;
}
其他的
{
$node_id[]=$line[0];
}
$targets=explode(“,”,$line[1]);
回显“节点”。$行[0]。.has.count($目标)。“链接\n”;
//在链接表中插入链接
foreach($targets作为$target)
{
如果(!是数值($target))
{
echo“line”.$i.“具有格式不正确的目标[”。trim($target)。“]\n”;
继续;
}
$sql=“插入链接(源id、目标id)值(““$line[0]”、“.”、“.trim($target)。”)”;
mysqli_查询($db,$sql)或die(“sql插入失败:”.$db::error);
}
如果($i%1000==0)
{
$node\u id=array\u unique($node\u id);
foreach($node\u id作为$node)
{
$sql=“插入节点(节点id)值(“$node.”);
mysqli_查询($db,$sql);
}
$node_id=array();
mysqli_提交($db);
mysqli_自动提交($db,false);
echo“提交到数据库\n\n”;
}
}
回声“
\n--
\n
\n浪费的秒数:”。(time()-$start);
我脑子里想的第一件事是使用
MySQLi
PDO
,这样你就可以利用预先准备好的语句。如果你的输入数据是CSV文件,也许你可以使用LOAD data Infle,更多信息:很抱歉没有从一开始就说明这一点:一切都是数字!A、 B也是数字。这将再次导致创建另一个huuuuge文件,甚至更大的文件,该文件必须写入数据库。显然,没有解析,但是…遗憾的是,这是在没有太多资源的vm上运行的。所以我决定跳过这场比赛