为了从外部文件加载数据,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
}
}
请注意,max_allowed_packet 的默认值在 MySQL 5.7 中仅为 1MB。如果您的复合插入语句超过此值,您将收到Packet too large 错误并且插入将失败。您可以将其最大调整为 1GB,但到那时您可能已经达到了LOAD DATA INFILE 更有效的阈值。