我有一个任务,我需要解析一个非常大的文件,并将结果写入一个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);
显然没有以任何方式优化速度。任何新的开始提示?我应该换用另一种语言吗?
答案 0 :(得分:2)
第一个优化是插入一个事务 - 每个100或1000行提交并开始一个新事务。显然,您必须使用支持事务的存储引擎。
然后使用top
命令观察CPU使用情况 - 如果你有多个内核,mysql进程没有做太多工作,PHP进程完成大部分工作,重写脚本以接受跳过n的参数从一开始的行,只导入10000行左右。然后启动脚本的多个实例,每个实例都有不同的起点。
第三种解决方案是将文件转换为带有PHP的CSV(根本不用INSERT,只写入文件),并使用LOAD DATA INFILE
作为m4t1t0建议。
答案 1 :(得分:1)
$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注入数据库(最好是在数据库所在的机器上)。