В двух словах
Есть ли способ в Perl использовать подготовленные операторы (для предотвращения внедрения SQL) для вставки 1 миллиона записей менее чем за 2 минуты в MySQL-таблицу?
Подробно
Существует онлайн-ресурс ( Wikimedia ), из которого я хочу загрузить файл ( dewiktionary-latest-all-title-in-ns0.gz ), который содержит почти 1 миллион названий статей (каждая статья представляет собой описание немецкого слова в Викисловарь). Я хочу проверять этот список один раз в неделю, а затем реагировать на новые или удаленные заголовки. Для этого я хочу автоматически загружать этот список раз в неделю и вставлять его в базу данных.
Хотя я доверяю Викимедиа, вам никогда не следует слишком сильно доверять вещам, приходящим из Интернета. Поэтому, чтобы предотвратить внедрение SQL и другие проблемы с безопасностью, я всегда использую подготовленные операторы в Perl. Убедитесь, что интерпретатор SQL не имеет возможности интерпретировать контент как код.
Обычно я бы сделал это так:
программа 1
#!/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 не видит это значение. Поэтому, что бы злоумышленник не написал в загруженный файл, это никогда не приведет к внедрению кода.
Но:
Это очень медленно. Загрузка выполняется в течение нескольких секунд, но цикл вставки работает более четырех часов.
Существует более быстрое решение, оно выглядит так:
программа 2
# 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 значений и выполнить их. Но это не важно для проблема, о которой я говорю здесь.)
Это работает очень быстро. Все значения (почти 1 миллион строк в новой таблице) вставляются менее чем за 2 минуты, это более чем в 100 раз быстрее.
Как видите, я создаю одно большое заявление, но без заполнителей. Я записываю значения непосредственно в команду SQL. Мне просто нужно было избежать обратной косой черты, которая будет интерпретироваться как escape-символы и одинарные кавычки, которые будут интерпретироваться как конец строки.
Но остальные значения остаются незащищенными и видимыми для интерпретатора SQL. Потенциальный злоумышленник может найти способ вставить код SQL в значения, которые будут выполнены. Это может повредить мою базу данных или даже предоставить права суперпользователя злоумышленнику. (повышение привилегий, вызванное внедрением кода)
Итак, вот мой вопрос:
Есть ли способ использовать подготовленные операторы, как в программе 1, даже для операторов, которые генерируются динамически, как в программе 2?
Или есть еще одна возможность быстро и безопасно вставить большие объемы данных в таблицу MySQL?