Det er muligt at forberede en bulk-indsæt-sætningsforespørgsel ved at konstruere den med det samme, men det kræver et par tricks. De vigtigste bits bruger str_pad()
at konstruere en forespørgselsstreng med variabel længde og bruge call_user_func_array()
at kalde bind_param()
med et variabelt antal parametre.
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;
}
Denne funktion tager din $db
som en mysqli
forekomst, et tabelnavn, en matrix af feltnavne og en flad matrix af referencer til værdier. Den indsætter op til 500 poster pr. forespørgsel, og genbruger forberedte udsagn, når det er muligt. Det returnerer true
hvis alle indsættelser lykkedes, eller false
hvis nogen af dem mislykkedes. Forbehold:
- Tabel- og feltnavnene er ikke escaped; Jeg lader det være op til dig at sikre, at de ikke indeholder backticks. Heldigvis bør de aldrig komme fra brugerinput.
- Hvis længden af
$values
er ikke et lige multiplum af længden af $fields
, vil den sidste del sandsynligvis fejle på forberedelsesstadiet. - Længden af
$types
på samme måde parameter skal matche længden af $fields
i de fleste tilfælde, især når nogle af dem er forskellige. - Den skelner ikke mellem de tre måder at fejle på. Den holder heller ikke styr på, hvor mange indsættelser der lykkedes, og den forsøger heller ikke at fortsætte efter en fejl.
Med denne funktion defineret kan din eksempelkode erstattes med noget som:
$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>";
}
Disse og-tegn er vigtige, fordi mysqli_stmt::bind_param
forventer referencer, som ikke leveres af call_user_func_array
i nyere versioner af PHP.
Du har ikke givet os den oprindelige udarbejdede erklæring, så du skal sandsynligvis justere tabel- og feltnavnene. Det ser også ud til, at din kode sidder inde i en løkke over $i
; i så fald kun for
løkken skal være inde i den ydre løkke. Hvis du tager de andre linjer uden for løkken, vil du bruge lidt mere hukommelse til at konstruere $inserts
array, til gengæld for meget mere effektive bulkskær.
Det er også muligt at omskrive insertBulkPrepared()
at acceptere et multidimensionelt array, hvilket eliminerer én kilde til potentiel fejl, men det kræver, at arrayet udflades, efter at det er fragmenteret.