複数のステートメント

MySQL は、ひとつのステートメントの文字列に、 複数のステートメントを埋め込むことをオプションでサポートしています。 しかし、そうするためには特別な操作が必要です。

複数のステートメント、 またはマルチクエリーは、 mysqli::multi_query を使って実行する必要があります。 文字列に埋め込まれる個々のステートメントは、 セミコロンで区切ります。 実行されたステートメントによって返される全ての結果セットは、 取得されなければいけません。

MySQL サーバーは、 結果セットを返すステートメントと、 返さないステートメントを、 ひとつの複数のステートメントに埋め込むことができます。

例1 複数のステートメント

<?php

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");

$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");

$sql = "SELECT COUNT(*) AS _num FROM test;
        INSERT INTO test(id) VALUES (1); 
        SELECT COUNT(*) AS _num FROM test; ";

$mysqli->multi_query($sql);

do {
    if ($result = $mysqli->store_result()) {
        var_dump($result->fetch_all(MYSQLI_ASSOC));
        $result->free();
    }
} while ($mysqli->next_result());

上の例の出力は以下となります。

array(1) {
  [0]=>
  array(1) {
    ["_num"]=>
    string(1) "0"
  }
}
array(1) {
  [0]=>
  array(1) {
    ["_num"]=>
    string(1) "1"
  }
}

セキュリティ上の考慮

mysqli::querymysqli::real_query は、 サーバーで複数のクエリを処理するための接続フラグを設定しません。 複数のステートメントを使うことで、 SQLインジェクション攻撃の被害を軽減するためには、 追加のAPI呼び出しが必要です。 攻撃者は、; DROP DATABASE mysql; SELECT SLEEP(999) のようなステートメントを追加しようとするかもしれません。 攻撃者がステートメントにSQLを追加することに成功したが、 mysqli::multi_query がそれを使わなければ、 サーバーは挿入された、有害なSQLステートメントを実行することはありません。

例2 SQLインジェクション

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = $mysqli->query("SELECT 1; DROP TABLE mysql.user");
?>

上の例の出力は以下となります。

PHP Fatal error:  Uncaught mysqli_sql_exception: You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to
use near 'DROP TABLE mysql.user' at line 1

プリペアドステートメント

プリペアドステートメントを複数のステートメントで使うことは、 サポートされていません。

参照

  • mysqli::query
  • mysqli::multi_query
  • mysqli::next_result
  • mysqli::more_results