1

生のクエリ文字列を作成し、1つのステートメントに数百行を挿入するスクリプトを作成しました。それは機能しますが、プリペアドステートメントが行う保護を提供しません。次に、スクリプトを変更して、プリペアドステートメントを追加しました。ただし、動作は大幅に遅くなります。プリペアドステートメントを含むスクリプトは、一度に数百行を挿入するのではなく、各プリペアド挿入ステートメントを一度に1行ずつ実行するため、生のクエリスクリプトよりも行の挿入にはるかに時間がかかります。

プリペアドステートメントコードのスニペットは次のとおりです。

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-> executeを1回実行するか、パフォーマンスを高速化できるその他のメソッドを実行する方法はありますか。

これまでに質問と回答があった場合は申し訳ありません。私はしばらく見回して、いくつかの同様の質問を見つけましたが、私が明確に求めていたものに答えるものは何もありませんでした。

4

1 に答える 1

2

オンザフライで作成して一括挿入ステートメント クエリを準備することは可能ですが、いくつかのコツが必要です。最も重要なビットは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;
}

この関数は$dbmysqliインスタンスとして、テーブル名、フィールド名の配列、および値への参照のフラットな配列を受け取ります。クエリごとに最大 500 レコードを挿入し、可能な場合は準備済みステートメントを再利用します。trueすべての挿入が成功したfalse場合、またはそれらのいずれかが失敗した場合に返されます。警告:

  • テーブル名とフィールド名はエスケープされません。バックティックが含まれていないことを確認するのはあなた次第です。幸いなことに、それらはユーザー入力から来ることはありません。
  • の長さが の長さの$values偶数倍でない場合$fields、最後のチャンクはおそらく準備段階で失敗します。
  • 同様に、パラメーターの長さは、ほとんどの場合、特に一部が異なる場合は$types、の長さと一致する必要があります。$fields
  • 失敗する 3 つの方法を区別しません。また、成功した挿入の数を追跡したり、エラー後に続行しようとしたりしません。

この関数を定義すると、サンプル コードを次のように置き換えることができます。

$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>";
}

最近のバージョンの PHP ではmysqli_stmt::bind_param提供されていない参照が期待されるため、これらのアンパサンドは重要です。call_user_func_array

元の準備済みステートメントが提供されていないため、おそらくテーブルとフィールドの名前を調整する必要があります。また、コードがループ内にあるように見えます$i。その場合、ループのみがfor外側のループ内にある必要があります。他の行をループの外に出すと、$insertsより効率的な一括挿入と引き換えに、配列を構築するためにより多くのメモリを使用することになります。

insertBulkPrepared()多次元配列を受け入れるように書き直して、潜在的なエラーの原因を 1 つ排除することもできますが、そのためには配列をチャンク化した後に配列を平坦化する必要があります。

于 2013-02-13T21:20:57.353 に答える