如何将数据写入文件以加载到MySQL数据库?

时间:2014-01-06 17:20:18

标签: mysql perl

我遇到某种I / O瓶颈,将解析后的数据插入MySQL数据库。因此,我不是解析文件并将每个记录单独插入数据库,而是将记录写入文件(如CSV),然后我可以手动将文件加载到数据库中。

如何使用Perl处理此问题?下面是当前将数据插入数据库的代码,但我想将数据写入文件,其格式使得以后尽可能轻松地将其插入MySQL。

#INSERT INTO ANNOUNCE TABLE
foreach my $au (@ANNOUNCED) {

    my $val=$au->[0];
    my $IP = $prefix->ip();
    my $subnetmask = $prefix->mask();


    $Announce_update->execute($IP,$subnetmask,$UpdateKey);

}

2 个答案:

答案 0 :(得分:1)

在这一点上,使用Text::CSV可能会让您的生活更轻松。安装该模块(如果该模块尚未在您的系统上),然后将此行添加到您的脚本中:

use Text::CSV ;

注释掉代码中的数据库插入位:

 # $dbh->prepare("SET FOREIGN_KEY_CHECKS = 0;")->execute();
 # $Announce_update->execute($IP,$subnetmask,$UpdateKey);

然后尝试抓取您想要的数据:

 my @csvdata = ($IP,$subnetmask,$UpdateKey); # assuming data is in these vars 
                                              # and they aren't references used 
                                              # by $Announce_update somehow

然后将其写入某处的CSV文件(从文档中删除),您将“手动”加载(我假设您的意思是使用MySQL控制台或CLI工具)。

  my $csv = Text::CSV->new ( { binary => 1 } )  
  open $fh, ">:encoding(utf8)", "csvdata.csv" or die ;
  $csv->print ($fh, $_) for @csvdata;

希望这会有所帮助 - 尽管手头有点短暂。即使它确实有效,我也不确定你这样做会让你的生活变得更轻松; - )

答案 1 :(得分:1)

要从外部文件加载数据,MySQL提供LOAD DATA INFILE命令。 LOAD DATA INFILE在输入文件格式方面相当灵活,允许您指定分隔符,EOL字符,是否引用字段等。它与Text::CSV一致很好用,您可以使用它输出包含数据的分隔文件。

首先,将您的数据写入文件:

use Text::CSV;

my $csv = Text::CSV->new({ eol => "\n" }) or die Text::CSV->error_diag();

my $infile = "/path/to/file";
open my $fh, ">", $infile or die $!;

for my $i (0..$#ANNOUNCED)
{
   # Don't end last line with '\n' or we'll get a garbage row when we load
   # to the database
   $csv->eol(undef) if $i == $#ANNOUNCED;

   # Generate the data to insert for this row

   # Write to file
   $csv->print($fh, [ $IP, $subnetmask, $UpdateKey ]);
}

# Close file handle to flush the buffer
close $fh;

请注意,写入后必须关闭文件句柄,否则MySQL可能无法获取所有数据。

接下来,加载文件:

my $query = "LOAD DATA LOCAL INFILE '$infile' INTO TABLE table
             FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'
             LINES TERMINATED BY '\\n'";

eval {
   # $dbh is an already open database handle
   my $rows_inserted = $dbh->do($query);
};
die $@ if $@;

LOCAL关键字会影响文件是否位于MySQL服务器上。如果您正在加载到位于localhost上的数据库,您可以不使用LOCAL关键字,这可能听起来很直观(使用LOCAL,客户端会将该文件的副本发送到服务器的临时目录和服务器从那里读取它,因此它是服务器的本地目录)。如果没有LOCAL关键字,该文件必须对所有人都可读(即* nix上至少0644的权限,因为您还必须写入它)。 MySQL也会根据是否使用LOCAL来查看相对路径的不同位置;有关详细信息,请参阅documentation

上述查询假设您的表只有三列。如果它有更多,您需要按照CSV 中显示的顺序指定要为其插入数据的列,例如:

my $query = "LOAD DATA LOCAL INFILE '$infile' INTO TABLE table
             FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'
             LINES TERMINATED BY '\\n'
             (ip, subnetmask, updatekey)";

化合物插入

如果您一次插入大量行,

LOAD DATA INFILE是目前最快的方法。但是,对于较小的插入,写入和读取临时文件的额外I / O开销(特别是如果使用LOCAL选项)使得它比使用像这样的复合插入更慢:

# Inserts 3 rows at once
INSERT INTO table VALUES (foo, foo, foo), (bar, bar, bar), (baz, baz, baz)

有一些神奇的行,LOAD DATA INFILE比复合插入更快。对于我的数据库和应用程序,我做了一些分析,发现这个数字大约为100,但对你来说几乎肯定是不同的。我编写了一个函数来根据要插入的数据行数选择最有效的方法:

sub insert_rows {
    my $data = shift; # Reference to an AoA

    my $num_rows = $#{ $data };

    if ($num_rows < 100) {
        # Generate compound insert statement
    }
    else {
        # LOAD DATA INFILE
    }
}

请注意,MySQL 5.7中的默认值max_allowed_packet仅为1MB。如果复合插入语句超出此范围,则会出现Packet too large错误,插入将失败。您可以将其调整为最大1GB,但到那时您可能已达到LOAD DATA INFILE更高效的阈值。