2013-02-13 2 views
1

Я создал скрипт, который создает необработанную строку запроса, а затем вставляет сотни строк в один оператор. Он работает, но не предлагает защиты, которые делают подготовленные заявления. Затем я модифицировал свой скрипт для добавления подготовленных операторов. Тем не менее, он работает значительно медленнее. Сценарий с подготовленными операторами занимает гораздо больше времени, чтобы вставлять строки, чем исходный сценарий запросов, из-за того, что скрипт запускается через каждый подготовленный оператор вставки по одной строке за раз, а не вставляет по сто строк за раз.MySQLi: Вставка нескольких строк с одним подготовленным оператором

Вот отрывок из подготовленного кода заявление:

for($j = 0; $j < $abilitiesMax - 2; $j++){ 
    $stmtAbility->bind_param('iiiii', $abilityArray[$i]["match_id"] , $abilityArray[$i]["player_slot"], 
    $abilityArray[$i][$j]["ability"], $abilityArray[$i][$j]["time"], $abilityArray[$i][$j]["level"]); 

    if( !($stmtAbility->execute())){  
    echo "<p>$db->error</p>"; 
    echo "<p>ERROR: when trying to insert abilities query</p>"; 
    } 
} 

Она получает работу, но только после того, как сотни и сотни вставок. Есть ли способ связать списки или массивы с аргументами bind_param() и просто запустить $ stmtAbility-> выполнить один раз или какой-либо другой метод, который может ускорить работу.

Извините, если об этом заранее задали и ответили. Некоторое время я огляделся и нашел некоторые подобные вопросы, но ничего не ответил на то, что я просил явно.

+0

По крайней мере, сильно связаны: http://stackoverflow.com/questions/4659317/bulk-parameterized-inserts? – fvu

+0

Возможно, вы захотите связать параметры перед циклом и затем установить значения связанных переменных в нем. – MichaelRushton

+0

@MichaelRushton Я думал о чем-то подобном, хотя я сомневаюсь, что вызовы 'bind_param' являются узким местом. Большая часть накладных расходов, вероятно, находится в 'execute()'. – Barmar

ответ

1

Можно подготовить запрос инструкции объемной вставки, построив его на лету, но это требует нескольких трюков. Наиболее важные биты используют 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(), чтобы принять многомерный массив, исключающий один источник потенциальной ошибки, но для этого требуется сгладить массив после его обрезки.

+0

Спасибо, очень полезно. – user1723535

Смежные вопросы