Можно подготовить запрос инструкции объемной вставки, построив его на лету, но это требует нескольких трюков. Наиболее важные биты используют str_pad()
для построения строки запроса переменной длины и с использованием call_user_func_array()
для вызова bind_param()
с переменным количеством параметров.
function insertBulkPrepared($db, $table, $fields, $types, $values) {
$chunklength = 500;
$fieldcount = count($fields);
$fieldnames = '`'.join('`, `', $fields).'`';
$prefix = "INSERT INTO `$table` ($fieldnames) VALUES ";
$params = '(' . str_pad('', 3*$fieldcount - 2, '?, ') . '), ';
$inserted = 0;
foreach (array_chunk($values, $fieldcount*$chunklength) as $group) {
$length = count($group);
if ($inserted != $length) {
if ($inserted) $stmt->close();
$records = $length/$fieldcount;
$query = $prefix . str_pad('', 3*$length + 2*($records - 1), $params);
#echo "\n<br>Preparing '" . $query . "'";
$stmt = $db->prepare($query);
if (!$stmt) return false;
$binding = str_pad('', $length, $types);
$inserted = $length;
}
array_unshift($group, $binding);
#echo "\n<br>Binding " . var_export($group, true);
$bound = call_user_func_array(array($stmt, 'bind_param'), $group);
if (!$bound) return false;
if (!$stmt->execute()) return false;
}
if ($inserted) $stmt->close();
return true;
}
Эта функция принимает ваш $db
как mysqli
например, имя таблицы, массив имен полей, а также плоский массив ссылок на значения. Он вставляет до 500 записей на запрос, повторно используя подготовленные заявления, когда это возможно. Он возвращает true
, если все вставки преуспели, или false
, если какой-либо из них не удался. Оговорки:
- Имена таблиц и полей не экранированы; Я оставляю это для вас, чтобы убедиться, что они не содержат обратных ссылок. К счастью, они никогда не должны поступать от ввода пользователем.
- Если длина
$values
не является даже кратной длине $fields
, последний фрагмент, вероятно, завершится неудачно на стадии подготовки.
- В большинстве случаев длина параметра
$types
должна соответствовать длине $fields
, особенно если некоторые из них отличаются.
- Он не проводит различия между тремя способами отказа. Он также не отслеживает, сколько вложений удалось, и не пытается продолжить работу после ошибки.
С помощью этой функции, определенной, ваш пример кода может быть заменен на что-то вроде:
$inserts = array();
for ($j = 0; $j < $abilitiesMax - 2; $j++) {
$inserts[] = &$abilityArray[$i]['match_id'];
$inserts[] = &$abilityArray[$i]['player_slot'];
$inserts[] = &$abilityArray[$i][$j]['ability'];
$inserts[] = &$abilityArray[$i][$j]['time'];
$inserts[] = &$abilityArray[$i][$j]['level'];
}
$fields = array('match_id', 'player_slot', 'ability', 'time', 'level');
$result = insertBulkPrepared($db, 'abilities', $fields, 'iiiii', $inserts);
if (!$result) {
echo "<p>$db->error</p>";
echo "<p>ERROR: when trying to insert abilities query</p>";
}
Эти амперсанды имеют важное значение, потому что mysqli_stmt::bind_param
ожидает ссылки, которые не предусмотренные call_user_func_array
в последних версиях PHP ,
Вы не дали нам оригинальный подготовленный оператор, поэтому вам, вероятно, нужно будет скорректировать имена таблиц и полей. Он также выглядит как ваш код находится внутри цикла над $i
; в этом случае петля for
должна находиться внутри внешнего контура. Если вы берете другие строки за пределами цикла, вы будете использовать немного больше памяти, построив массив $inserts
, в обмен на гораздо более эффективные объемные вставки.
Также можно переписать insertBulkPrepared()
, чтобы принять многомерный массив, исключающий один источник потенциальной ошибки, но для этого требуется сгладить массив после его обрезки.
По крайней мере, сильно связаны: http://stackoverflow.com/questions/4659317/bulk-parameterized-inserts? – fvu
Возможно, вы захотите связать параметры перед циклом и затем установить значения связанных переменных в нем. – MichaelRushton
@MichaelRushton Я думал о чем-то подобном, хотя я сомневаюсь, что вызовы 'bind_param' являются узким местом. Большая часть накладных расходов, вероятно, находится в 'execute()'. – Barmar