使用Perl脚本为非常大的文件(60GB)提高MySQL中表中Insert的速度

时间:2015-01-29 13:07:06

标签: mysql perl

我使用Perl脚本从xml文件(包含超过10亿行)填充MySQL中的表,以查找感兴趣的行。该脚本运行非常顺利,直到第15M行,但之后它开始以指数方式增加一些。 就像第一个1000000行一样,需要大约12秒来解析并将其写入数据库,但是在15M行之后需要时间来解析和写入相同数量的行~43s。

我将innodb_buffer_pool_size从128M增加到1024M,如

所示

Insertion speed slowdown as the table grows in mysql answered by Eric Holmberg

时间要求分别降至~7s和~32s,但由于我需要处理大量文件并且时间要求不断增加,因此时间要求仍然很慢。

此外,我删除了任何Primary keyIndex的创建,认为它可能会导致一些问题(不确定)

以下是代码段:

$dbh = DBI->connect('dbi:mysql:dbname','user','password') or die "Connection Error: $DBI::errstr\n";

$stmt = "DROP TABLE IF EXISTS dbname";
$sth = $dbh->do($stmt);

$sql = "create table db(id INTEGER not null, type_entry VARCHAR(30) not null, entry VARCHAR(50))";
$sth = $dbh->prepare($sql);
$sth->execute or die "SQL Error: $DBI::errstr\n";
open my $fh1, '<', "file.xml" or die $!;

while (<$fh1>)
{
    if ($_=~ m/some pattern/g)
    {
        $_=~ s/some pattern//gi;
        $id = $_;
    }
    elsif ($_=~ m/some other pattern/)
    {
        $_=~ s/\s|(\some other pattern//gi;
        $type = $_;
    }
    elsif ($_=~ m/still some other pattern/)
    {
        $_=~ s/still some other pattern//gi;
        $entry = $_;
    }
    if($id ne "" && $type ne "" && $entry ne "")
    {
        $dbh->do('INSERT INTO dbname (id, type_entry, species) VALUES (?, ?, ?)', undef, $id, $type, $entry);
    }
}

该数据库将包含约170万条目。还有什么办法可以减少时间?

先谢谢

编辑1:

谢谢大家的帮助 从早上起,我一直在努力实施已经告知的所有内容,并且正在检查我是否能够大幅减少时间。 所以我做了什么:

  1. 我按照@ikegami的说法删除了两次匹配模式,但是我确实需要替换。
  2. 我利用hash(由@ikegami讲述)
  3. 我使用LOAD DATA LOCAL INFILE(由@ikegami,@ ysth和@ThisSuitIsBlackNot讲述)。但我已将其嵌入到我的代码中以获取文件然后将其处理到数据库。此处的文件由脚本动态编写,当它达到1000个条目时,将写入db。
  4. 连续1000000行的运行时间

    13 s

    11 s

    24 s

    22 s

    35 s

    34 s

    47 s

    45 s

    58 s

    57 s .....

    (想发布图片但是......声誉)

    编辑2:

    我检查了时间并跟踪了脚本将其写入数据库所需的时间;令我惊讶的是它是线性的。现在我从这里得出的结论是,while循环存在一些问题,我相信它会以指数方式增加时间,因为它必须转到每次迭代的行号,并且当它深入到文件中时它必须计算更多的数字线到达下一行。

    对此的任何评论

    编辑3

    $start_time = time();
    $line=0;
    open my $fh1, '<', "file.xml" or die $!;
    while (<$fh1>)
    {
    $line++;
    %values;
    if ($_=~ s/foo//gi)
    {
    $values{'id'} = $_;
    }
    elsif ($_=~ s/foo//gi)
    {
    $values{'type'} = $_;
    }
    elsif ($_=~ s/foo//gi)
    {
    $values{'pattern'} = $_;
    }
    if (keys(%values) == 3)
    {
    $no_lines++;
    open FILE, ">>temp.txt" or die $!;
    print FILE "$values{'id'}\t$values{'type'}\t$values{'pattern'}\n";
    close FILE;
    if ($no_lines == 1000)
    {
    #write it to database using `LOAD DATA LOCAL INFILE` and unlink the temp.txt file
    }
    undef %values;
    }
    if($line == ($line1+1000000))
    {
    $line1=$line;
    $read_time = time();
    $processing_time = $read_time - $start_time - $processing_time;
    print "xml file parsed till line $line, time taken $processing_time sec\n";
    }
    }
    

    解答:

    首先,我要道歉,花了这么长时间才回复;当我从Perl的root到top再次开始时,这次use strict显而易见,这有助于我保持线性时间。在处理大型Xml文件时,使用XML Parsers也是一件好事。

    而且要添加更多内容,MySQL插入的速度并不总是线性的

    感谢大家的帮助和建议

1 个答案:

答案 0 :(得分:1)

我猜测瓶颈是实际的插入。生成INSERT语句,将它们放在一个文件中,然后使用mysql命令行工具执行该文件肯定会快一点。

您可以尝试创建插入大量行与单个语句的INSERT语句。

或者最好完全避免INSERT语句。我认为mysql命令行工具可以从CSV文件填充数据库。这可能会产生更快的速度。

更好的是,如果您有权访问托管数据库的计算机的文件系统,则可以使用LOAD DATA INFILE


您的Perl代码也可以使用一些清理工具。

  • 您搜索每个模式两次?变化

    if (/foo/) { s/foo//gi; $id = $_ }
    

    if (s/foo//gi) { $id = $_ }
    
  • 实际上,你需要替换吗?这可能会更快

    if (/foo (.*)/) { $id = $1 }
    
  • 看起来您可以按照

    的方式做更多事情
    my ($k, $v) = split(/:\s*/);
    $row{$k} = $v;
    

    而不是那个巨人if

  • 此外,如果您使用哈希,那么您可以使用以下内容进行最后检查:

    if (keys(%row) == 3)