mysql - 快速质量插入的准备语句

标签 mysql perl

一言以蔽之

在 Perl 中有没有办法使用准备好的语句(以防止 SQL 注入(inject))在不到 2 分钟的时间内将 100 万条记录插入 MySQL 表?


详细

有一个在线资源 ( Wikimedia ),我想从中下载一个文件 (dewiktionary-latest-all-titles-in-ns0.gz),其中包含近 100 万个标题文章(每篇文章都是维基词典中一个德语单词的描述)。我想每周检查一次此列表,然后对新的或删除的标题使用react。为此,我想每周自动下载一次此列表并将其插入数据库。

虽然我信任维基媒体,但您永远不要过分信任来自互联网的任何内容。因此,为了防止 SQL 注入(inject)和其他安全问题,我总是在 Perl 中使用准备好的语句,确保 SQL 解释器没有机会将内容解释为代码。

通常我会这样做:

方案一

#!/usr/bin/perl -w

use strict;
use warnings;
use LWP::UserAgent;
use DBI;

# DOWNLOAD FROM INTERNET =========================
# create User-Agent:
my $ua = LWP::UserAgent->new;
# read content from Internet
my $response = $ua->get('https://<rest_of_URL>');
# decode content
my $content = $response->decoded_content;

#turn into a list
my @list = split(/\n/,$content);

# STORE IN DATABASE ==============================
# connect with database (create DataBase-Handle):
my $dbh = DBI->connect(
    'DBI:mysql:database=<name_of_DB>;host=localhost',
    '<user>','<password>',
    {mysql_enable_utf8mb4 => 1}
);
# SQL statement
my $SQL = 'INSERT INTO `mytable`(`word`) VALUES(?)';
# prepare statement (create Statement Handle)
my $SH = $dbh->prepare($SQL);
#execute in a loop
foreach my $word (@list) {
    $SH->execute($word);
}
# disconnect from database
$dbh->disconnect;
# end of program
exit(0);

注意这一行(第27行):

my $SQL = 'INSERT INTO `mytable`(`word`) VALUES(?)';

SQL 命令行中有一个问号作为占位符。 在下一行中准备了这个 SQL 命令行(即创建了一个准备好的语句),并在循环中执行了这个语句,这意味着每次都会将一个新值($word)插入到表中,而无需任何执行这个值的机会,因为 SQL 解释器看不到这个值。因此,无论攻击者如何写入我下载的文件,都不会导致代码注入(inject)。

但是:
这很慢。下载在几秒钟内完成,但插入循环运行了四个多小时。


有一个更快的解决方案,它是这样的:

方案二

# The code above the SQL-Statement is exactly
# the same as in the 1st program
#-------------------------------------------------
# SQL statement
my $SQL = 'INSERT INTO `mytable`(`word`) VALUES ';  # <== NO '?'!
# attach values in a loop
# initiate comma with empty string
my $comma = '';
foreach my $word (@list) {
    # escape escapecharacter
    $word =~ s/\\/\\\\/g;
    # escape quotes
    $word =~ s/'/\\'/g;
    # put the value in quotes and then in brackets, add the comma
    # and then append it to the SQL command string
    $SQL .= $comma."('".$word."')";
    # comma must be a comma
    $comma = ',';
}
# Now prepare this mega-statement
my $SH = $dbh->prepare($SQL);
# and execute it without any parameter
$SH->execute();
# disconnect from database
$dbh->disconnect;
# end of program
exit(0);

(这是简化的,因为 SQL 语句会变得太长而无法被 MySQL 接受。您需要将其拆分为大约 5000 个值的部分并执行它们。但这对于我在这里谈论的问题。)

这运行得非常快。所有值(新表中的近 100 万行)都在不到 2 分钟内插入,速度快了 100 多倍。

如您所见,我创建了一个大语句,但没有占位符。我将值直接写入 SQL 命令。我只需要转义将被解释为转义字符的反斜杠和将被解释为字符串结尾的单引号。

但是其余的值仍然不 protected 并且对 SQL 解释器可见。潜在的攻击者可能会找到一种方法将 SQL 代码插入到将要执行的值中。这可能会损坏我的数据库,甚至可能会授予攻击者 super 用户权限。 (代码注入(inject)引起的权限提升)


所以,这是我的问题:

有没有办法像程序 1 中那样使用准备好的语句,即使是像程序 2 中那样动态生成的语句?

或者是否有另一种快速且安全将大量数据插入 MySQL 表的可能性?

最佳答案

您的斜体小注释实际上非常相关:

(This is simplified, since the SQL statement would become too long to be accepted by MySQL. You need to split it up in sections of about 5000 values and execute them. But this is not important for the problem I'm talking about here.)

我认为您的“未经准备的陈述”(不是真正的术语)方法更快,因为您一次批量加载 5000 条记录而不是一条一条地加载,而不是因为它不是准备好的陈述。

尝试使用 5000 个 ? 构建准备好的语句,如下所示:

my $SQL = 'INSERT INTO `mytable`(`word`) VALUES ' . '(?),'x4999 . '(?)';

然后构建一次包含 5000 个单词的列表,并以此执行您准备好的语句。您必须使用最后一批中适当数量的单词的第二个动态生成的准备好的语句来处理最后一组(大概)少于 5000 个单词。

您还可以查看 LOAD DATA INFILE 以进行批量加载。

关于mysql - 快速质量插入的准备语句,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52025433/

相关文章:

php - Eloquent hasOne 关系即使存在相关记录也返回 NULL

mysql - MySQL Workbench 中的正向工程输出错误 1064

php - 将数据库值传递给 JavaScript

perl - Perl 中的 `use base` 和 @ISA 有什么区别?

php - MySQL查询网页显示mysql_numrows()错误

mysql - 如何在sql更新查询中忽略双引号以更改列值(值为XML)

linux - 在 Perl 中等待输入的定义时间段

java - 在 Java 中捕获 Perl 异常

perl - 使用 CPAN 安装 Perl 模块时找不到 dmake

perl - 我的 CPAN 的 Perl 模块无法安装,我该怎么办?