2013-02-06 22 views
3

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

事情不是“怎么做”,但如何做到这一点快。我的第一个方法是在没有任何速度优化的情况下使用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

第一我脑子里的事情就是使用'MySQLi'或'PDO',这样你就可以利用准备好的语句。 – Passerby

+1

如果你的输入数据是一个CSV文件,也许你可以使用LOAD DATA INFILE,更多信息:http://dev.mysql.com/doc/refman/5.0/en/load-data.html – m4t1t0

回答

2

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

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

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

+0

不幸的是,这是运行在没有太多资源的虚拟机上。所以我决定从一开始就跳过第二个解决方案。无论如何,第一个解决方案工作得很好。看到我的帖子下面... – xenonite

0

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

如果(!计数($线)= 2 || is_numeric(修剪($行[0])))

微调这里是多余的 - 空白不改变行为is_numberic。但是你已经说过,这条线的开头是一个字母 - 因此这总是会失败。

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

+0

对不起,从一开始就没有说清楚:一切都在数字! A,B也是数字。 – xenonite

0

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

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

+0

好吧,这将再次导致创建另一个huuuuuuuge - 甚至更大的文件,必须写入数据库。显然然后没有解析,但是... – xenonite

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); 
+1

进一步的优化将包括:a)编译包含多个数据记录的sql插入语句(这现在相当不理想......)b)使用预处理语句c)运行该脚本并行地解析文件的不同部分 – xenonite