1
0
mirror of https://github.com/php/doc-ja.git synced 2026-03-23 22:52:11 +01:00
Files
archived-doc-ja/reference/mysqli/quickstart.xml

1804 lines
60 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 60a292997b3c6341cf52099d901aa0b5f8673d87 Maintainer: mumumu Status: ready -->
<chapter xml:id="mysqli.quickstart" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>クイックスタートガイド</title>
<para>
このクイックスタートガイドを読むと、
PHP の MySQL API に親しみ、選択するのに役立ちます。
</para>
<para>
このクイックスタートガイドは、
mysqli 拡張モジュールの概要を示しています。
コードの例は API の主要な機能を全て示しています。
データベースの概念についてのページでは、
MySQL に固有の概念について、必要な程度まで説明しています。
</para>
<para>
PHP 言語によるプログラミングと、SQL 言語、
そして MySQL サーバーの基礎に親しんでいることが必須です。
</para>
<section xml:id="mysqli.quickstart.dual-interface">
<title>手続き型とオブジェクト指向インターフェイス</title>
<para>
mysqli 拡張モジュールは、
ふたつのインターフェイスを提供しています。
手続き型とオブジェクト指向のインターフェイスです。
</para>
<para>
古い mysql 拡張モジュールから移行するユーザーは、
手続き型のインターフェイスを好むかもしれません。
手続き型のインターフェイスは、古い mysql 拡張モジュールのそれに似ています。
多くの場合、関数名はプレフィックスのみが異なりますが、
mysqli の関数によっては、最初の引数に接続ハンドルを取るものもあります。
一方で、それと一致する古い mysql 拡張モジュールのインターフェイスは、
オプションの接続ハンドルを最後にとるようになっています。
</para>
<para>
<example>
<title>古い mysql 拡張モジュールから移行する簡単な方法</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = mysqli_connect("example.com", "user", "password", "database");
$result = mysqli_query($mysqli, "SELECT 'Please do not use the deprecated mysql extension for new development. ' AS _msg FROM DUAL");
$row = mysqli_fetch_assoc($result);
echo $row['_msg'];
$mysql = mysql_connect("example.com", "user", "password");
mysql_select_db("test");
$result = mysql_query("SELECT 'Use the mysqli extension instead.' AS _msg FROM DUAL", $mysql);
$row = mysql_fetch_assoc($result);
echo $row['_msg'];
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Please do not use the deprecated mysql extension for new development. Use the mysqli extension instead.
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">オブジェクト指向インターフェイス</emphasis>
</para>
<para>
伝統的な手続き型のインターフェイスに加えて、
ユーザーはオブジェクト指向インターフェイスを選ぶことができます。
このドキュメントは、
オブジェクト指向インターフェイスも使えるように整理されています。
オブジェクト指向インターフェイスは、
目的に応じてグループ化されていますし、
使い始めるのも簡単です。リファレンスのページでは、
手続き型に加えて、オブジェクト指向インターフェイスの使い方も示しています。
</para>
<para>
この2つのインターフェイスの間に、
目立ったパフォーマンスの違いはありません。
ユーザーは、自分の好みに応じてインターフェイスを選択できます。
</para>
<para>
<example>
<title>オブジェクト指向と手続き型のインターフェイス</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = mysqli_connect("example.com", "user", "password", "database");
$result = mysqli_query($mysqli, "SELECT 'A world full of ' AS _msg FROM DUAL");
$row = mysqli_fetch_assoc($result);
echo $row['_msg'];
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = $mysqli->query("SELECT 'choices to please everybody.' AS _msg FROM DUAL");
$row = $result->fetch_assoc();
echo $row['_msg'];
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
A world full of choices to please everybody.
]]>
</screen>
</example>
</para>
<para>
このクイックスタートガイドでは、
オブジェクト指向のインターフェイスを使います。
なぜなら、リファレンスのページが、そのように整理されているからです。
</para>
<para>
<emphasis role="bold">ふたつのスタイルを混ぜる</emphasis>
</para>
<para>
手続き型と、オブジェクト指向のやり方を切り替えることもできます。
これら両方のスタイルを混ぜることは、
コードの明確さやコーディングスタイルを統一する理由から、
推奨できません。
</para>
<para>
<example>
<title>悪いコーディングスタイル</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = mysqli_query($mysqli, "SELECT 'Possible but bad style.' AS _msg FROM DUAL");
if ($row = $result->fetch_assoc()) {
echo $row['_msg'];
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Possible but bad style.
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">参照</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli_result::fetch_assoc</methodname></member>
<member><link linkend="mysqli.connect-errno">$mysqli::connect_errno</link></member>
<member><link linkend="mysqli.connect-error">$mysqli::connect_error</link></member>
<member><link linkend="mysqli.errno">$mysqli::errno</link></member>
<member><link linkend="mysqli.error">$mysqli::error</link></member>
<member><link linkend="mysqli.summary">The MySQLi Extension Function Summary</link></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.connections">
<title>データベース接続</title>
<para>
MySQL は、サーバーに接続するために、
異なるトランスポート層をサポートしています。
TCP/IP, Unix ドメインソケット、
そして、Windows の名前付きパイプです。
</para>
<para>
ホスト名 <literal>localhost</literal> には特別な意味があります。
これは、Unixドメインソケットにバインドされます。
localhost に対して TCP/IP 接続を開くには、
<literal>localhost</literal> の代わりに、
<literal>127.0.0.1</literal> を使わなければいけません。
</para>
<para>
<example>
<title>localhost が持つ特別な意味</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("localhost", "user", "password", "database");
echo $mysqli->host_info . "\n";
$mysqli = new mysqli("127.0.0.1", "user", "password", "database", 3306);
echo $mysqli->host_info . "\n";
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Localhost via UNIX socket
127.0.0.1 via TCP/IP
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">デフォルトの接続パラメータ</emphasis>
</para>
<para>
接続関数によっては、
引数を省略できる場合があります。
引数が与えられない場合、
拡張モジュールは PHP の設定ファイルに設定されたデフォルト値を使おうとします。
</para>
<para>
<example>
<title>デフォルト値を設定する</title>
<programlisting role="ini">
<![CDATA[
mysqli.default_host=192.168.2.27
mysqli.default_user=root
mysqli.default_pw=""
mysqli.default_port=3306
mysqli.default_socket=/tmp/mysql.sock
]]>
</programlisting>
</example>
</para>
<para>
結果として決まった引数の値が、
拡張モジュールが使うクライアントライブラリに渡されます。
クライアントライブラリが空、または未設定の引数を検知した場合、
ライブラリに組み込まれたデフォルト値が使われます。
</para>
<para>
<emphasis role="bold">ライブラリに組み込まれたデフォルト値</emphasis>
</para>
<para>
host の値が未設定または空の場合、
クライアントライブラリはデフォルト値を
Unixドメインソケットの <literal>localhost</literal> とします。
socket の値が未設定または空の場合、
かつ Unixドメインソケットの接続がリクエストされた場合、
デフォルトのソケット <literal>/tmp/mysql.sock</literal>
を使って接続を試みます。
</para>
<para>
Windows の場合、
ホスト名 <literal>.</literal>
は、クライアントライブラリによって
Windows の名前付きパイプベースの接続をオープンするものと解釈されます。
この場合、socket のパラメータはパイプの名前として解釈されます。
この値が未設定または空の場合、
ソケット(パイプ名)のデフォルトは
<literal>\\.\pipe\MySQL</literal> となります。
</para>
<para>
Unixドメインソケットベース、
および Windows の名前付きパイプベースの接続が両方確立されず、
port パラメータが未設定の場合、
ライブラリはデフォルトのポートを
<literal>3306</literal> に設定します。
</para>
<para>
<link linkend="mysqlnd.overview">mysqlnd</link>
ライブラリと
MySQL Client Library (libmysqlclient) は、
デフォルト値を決めるためのロジックは同じです。
</para>
<para>
<emphasis role="bold">接続オプション</emphasis>
</para>
<para>
接続オプションも利用可能です。
たとえば、接続時に実行される初期化コマンドや、
特定の文字セットを使うようにリクエストするなど、です。
接続オプションはネットワーク接続が確立される前に設定しなくてはいけません。
</para>
<para>
接続オプションを設定するために、
接続操作は3つの段階を踏んで行われます。
<function>mysqli_init</function>
または <methodname>mysqli::__construct</methodname>
を使って接続ハンドルを初期化し、
<methodname>mysqli::options</methodname> を使って
リクエストされた接続オプションを設定し、
<methodname>mysqli::real_connect</methodname>
で、ネットワーク接続を確立します。
</para>
<para>
<emphasis role="bold">接続のプーリング</emphasis>
</para>
<para>
mysqli 拡張モジュールは、
データベースへの持続的接続をサポートしています。
これは、特別な種類の接続で、プーリングされます。
デフォルトでは、
スクリプトによってオープンされたデータベース接続それぞれが、
ユーザによって明示的に閉じられるか、
スクリプトによって自動的に開放されます。
持続的接続はそうではなく、
開放する代わりに、後に再利用するためにプールに戻します。
同じサーバーへの接続が同じユーザ名、
パスワード、ソケット、ポート、
そしてデフォルトデータベースを指定してオープンされた場合、
接続が再利用され、接続のオーバーヘッドが軽減されます。
</para>
<para>
PHP のプロセスごとに、mysqli の接続プールが使われます。
webサーバーがどのように運用されるかによりますが、
PHP のプロセスはひとつ以上のリクエストを処理する可能性があります。
よって、プーリングされた接続は
ひとつ以上のスクリプトから後に使われる可能性があるのです。
</para>
<para>
<emphasis role="bold">持続的接続</emphasis>
</para>
<para>
あるホスト名、ユーザ名、パスワード、ソケット、ポート、
そしてデフォルトのデータベースの組み合わせについて、
未使用の持続的接続がプールに見つからない場合、
mysqli は新しい接続をオープンします。
持続的接続を使うかどうかは、
<link linkend="ini.mysqli.allow-persistent">mysqli.allow_persistent</link>
を使って制御できます。
スクリプトがオープンする接続の合計数は、
<link linkend="ini.mysqli.max-links">mysqli.max_links</link>
で制御できます。
PHP プロセス毎に使う持続的接続の最大数は
<link linkend="ini.mysqli.max-persistent">mysqli.max_persistent</link>
で制御できます。
Webサーバーは多くの
PHP プロセスをforkさせる可能性があることに注意して下さい。
</para>
<para>
持続的接続についてよくある不満が、
再利用する前にステートがリセットされないというものです。
たとえば、
オープンされ、終了していないトランザクションは
自動的にロールバックされません。
しかし同時に
接続をプールに戻し、
再利用するまでの間に行われた認証情報の変更も反映されません。
これは、望ましくない副作用と見なされるかもしれません。
一方で、<literal>persistent</literal> という名前は、
ステートを維持することを約束していると理解されるかもしれません。
</para>
<para>
mysqli 拡張モジュールは、
持続的接続について、両方の解釈をサポートしています:
ステートを維持するか、
再利用する前にステートをリセットするか、です。
デフォルトはリセットです。
持続的接続が再利用される前に、
mysqli 拡張モジュールは暗黙のうちに
ステートをリセットするために
<methodname>mysqli::change_user</methodname> をコールします。
持続的接続は、
これによってちょうど今オープンされたかのようにユーザの前に出現します。
以前利用されていた状態は見えません。
</para>
<para>
<methodname>mysqli::change_user</methodname>
をコールすると、コストが高く付きます。
パフォーマンスを最大にするために、
ユーザーはコンパイルフラグ
<constant>MYSQLI_NO_CHANGE_USER_ON_PCONNECT</constant>
を設定して、拡張モジュールをリコンパイルしたいかもしれません。
</para>
<para>
安全な振る舞いと、
最大のパフォーマンスのどちらを取るかは、
ユーザに任されています。
どちらを選んでも、最適化の目的としては正当なものです。
使いやすさを求めれば、
パフォーマンスを犠牲にして安全な振る舞いを行わせる
ことがデフォルトになります。
</para>
<para>
<emphasis role="bold">参照</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><function>mysqli_init</function></member>
<member><methodname>mysqli::options</methodname></member>
<member><methodname>mysqli::real_connect</methodname></member>
<member><methodname>mysqli::change_user</methodname></member>
<member><link linkend="mysqli.get-host-info">$mysqli::host_info</link></member>
<member><link linkend="mysqli.configuration">MySQLi Configuration Options</link></member>
<member><link linkend="features.persistent-connections">Persistent Database Connections</link></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.statements">
<title>ステートメントの実行</title>
<para>
ステートメントは、
<methodname>mysqli::query</methodname>,
<methodname>mysqli::real_query</methodname>,
<methodname>mysqli::multi_query</methodname> を使って実行できます。
<methodname>mysqli::query</methodname>
がもっともよく使われますが、
ステートメントの実行と、
バッファリングされた結果セットの取得を一回の呼び出しで組み合わせることもできます。
<methodname>mysqli::query</methodname> の呼び出しは、
<methodname>mysqli::real_query</methodname> の後に
<methodname>mysqli::store_result</methodname>
を呼び出すことと同じです。
</para>
<para>
<example>
<title>クエリを実行する</title>
<programlisting role="php">
<![CDATA[
<?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)");
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">バッファリングされた結果セット</emphasis>
</para>
<para>
ステートメントを実行した後、
結果は一度に全部取得するか、
一行ずつ読み取ることのどちらかができます。
クライアント側で結果セットをバッファリングすることで、
サーバーは結果セットに関連付けられたリソースをできるだけ早く開放できます。
一般的な話ですが、
クライアントが結果セットを処理する速度は遅いです。
そのため、バッファリングされた結果セットを使うことを推奨します。
<methodname>mysqli::query</methodname>
は、ステートメントの実行と結果セットのバッファリングを組み合わせます。
</para>
<para>
PHP アプリケーションは、
バッファリングされた結果セットを通して、結果を自由に操作できます。
そうすることで、結果セットがクライアント側のメモリに保持されるため、
高速です。
サーバー側をスケールさせるより、
クライアント側でスケールさせるほうが容易なことがある、
ということを頭に入れておいて下さい。
</para>
<para>
<example>
<title>バッファリングされた結果セットを操作する</title>
<programlisting role="php">
<![CDATA[
<?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)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$result = $mysqli->query("SELECT id FROM test ORDER BY id ASC");
echo "Reverse order...\n";
for ($row_no = $result->num_rows - 1; $row_no >= 0; $row_no--) {
$result->data_seek($row_no);
$row = $result->fetch_assoc();
echo " id = " . $row['id'] . "\n";
}
echo "Result set order...\n";
foreach ($result as $row) {
echo " id = " . $row['id'] . "\n";
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Reverse order...
id = 3
id = 2
id = 1
Result set order...
id = 1
id = 2
id = 3
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">結果セットをバッファリングしない場合</emphasis>
</para>
<para>
クライアント側のメモリが不足し、
かつサーバー側のリソースをできるだけ早く開放することで、
サーバーの負荷を下げる必要がない場合、
結果セットのバッファリングを無効にできます。
結果セットをバッファリングしない場合、
全ての行を読み取るまで、
結果を操作することはできません。
</para>
<para>
<example>
<title>結果セットをバッファリングせず、結果を操作する</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli->real_query("SELECT id FROM test ORDER BY id ASC");
$result = $mysqli->use_result();
echo "Result set order...\n";
foreach ($result as $row) {
echo " id = " . $row['id'] . "\n";
}
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">結果セットの値のデータ型</emphasis>
</para>
<para>
<methodname>mysqli::query</methodname>,
<methodname>mysqli::real_query</methodname>,
<methodname>mysqli::multi_query</methodname>
関数は、準備されていないステートメントを実行するために使います。
MySQL のクライアントサーバープロトコルのレベルでは、
<literal>COM_QUERY</literal>
コマンドとテキストプロトコルをステートメントの実行に使います。
テキストプロトコルでは、
MySQL サーバーは結果セットの全てのデータを、
送信する前に文字列に変換します。
この変換は、
SQL の結果セットの、
カラムのデータ型が何であっても行われます。
MySQL のクライアントライブラリは、
全てのカラムの値を文字列として受け取ります。
MySQL 側のネイティブなデータ側に戻すために、
それ以上のキャストがクライアント側で行われることはありません。
キャストするのではなく、
全ての値が PHP の文字列型として返されます。
</para>
<para>
<example>
<title>テキストプロトコルは、文字列を返すのがデフォルト</title>
<programlisting role="php">
<![CDATA[
<?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, label CHAR(1))");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'a')");
$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1");
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (string)
label = a (string)
]]>
</screen>
</example>
</para>
<para>
mysqlnd ライブラリを使っている場合に、
<constant>MYSQLI_OPT_INT_AND_FLOAT_NATIVE</constant>
接続オプションを設定すると、
int や float 型のカラムの値を、
PHP の数値型に変換できます。
これを設定すると、mysqlnd ライブラリは、
結果セットのメタデータのカラム型を調べ、
PHP のデータ型の範囲が許可している場合に、
数値のSQLカラムの値を PHP の数値に変換します。
こうすることで、たとえば、
SQL の INT カラムを、数値として返すことができます。
</para>
<para>
<example>
<title>mysqlnd と接続オプションを設定することで、ネイティブのデータ型を扱う</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli();
$mysqli->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, 1);
$mysqli->real_connect("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label CHAR(1))");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'a')");
$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1");
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer)
label = a (string)
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">参照</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::options</methodname></member>
<member><methodname>mysqli::real_connect</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::use_result</methodname></member>
<member><methodname>mysqli::store_result</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.prepared-statements">
<title>プリペアドステートメント</title>
<para>
MySQL データベースは、
プリペアドステートメントをサポートしています。
プリペアドステートメント、
またはパラメータ化したステートメントは、
同じステートメントを繰り返し、
高い効率で実行すると同時に、
SQLインジェクションから守ります。
</para>
<para>
<emphasis role="bold">基本的なワークフロー</emphasis>
</para>
<para>
プリペアドステートメントの実行は、
ふたつの段階を踏んで行われます:
準備と実行です。
準備の段階では、
ステートメントのテンプレートがデータベースサーバーに送信されます。
サーバーは文法のチェックを行い、
サーバーの内部リソースを後に再利用するために初期化しておきます。
</para>
<para>
MySQL サーバーは名前を指定せず、
位置を指定できるプレースホルダーを
<literal>?</literal> によってサポートしています。
</para>
<para>
準備の後、実行が行われます。
実行する間、
クライアントはパラメータの値をバインドし、サーバーに送信します。
サーバーはステートメントをバインドされた値とともに、
以前作成した内部リソースを使って実行します。
</para>
<para>
<example>
<title>プリペアドステートメント</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Non-prepared statement */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
/* Prepared statement, stage 1: prepare */
$stmt = $mysqli->prepare("INSERT INTO test(id, label) VALUES (?, ?)");
/* Prepared statement, stage 2: bind and execute */
$id = 1;
$label = 'PHP';
$stmt->bind_param("is", $id, $label); // "is" means that $id is bound as an integer and $label as a string
$stmt->execute();
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">繰り返し実行させる</emphasis>
</para>
<para>
プリペアドステートメントは、
繰り返し実行させることができます。
実行させる度に、
バインドされた現在の値が評価され、
サーバーに送られます。
ステートメントは再度パースされません。
ステートメントのテンプレートもサーバーに再度送信されません。
</para>
<para>
<example>
<title>INSERT を一度だけ準備し、複数回実行する</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Non-prepared statement */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
/* Prepared statement, stage 1: prepare */
$stmt = $mysqli->prepare("INSERT INTO test(id, label) VALUES (?, ?)");
/* Prepared statement, stage 2: bind and execute */
$stmt->bind_param("is", $id, $label); // "is" means that $id is bound as an integer and $label as a string
$data = [
1 => 'PHP',
2 => 'Java',
3 => 'C++'
];
foreach ($data as $id => $label) {
$stmt->execute();
}
$result = $mysqli->query('SELECT id, label FROM test');
var_dump($result->fetch_all(MYSQLI_ASSOC));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(3) {
[0]=>
array(2) {
["id"]=>
string(1) "1"
["label"]=>
string(3) "PHP"
}
[1]=>
array(2) {
["id"]=>
string(1) "2"
["label"]=>
string(4) "Java"
}
[2]=>
array(2) {
["id"]=>
string(1) "3"
["label"]=>
string(3) "C++"
}
}
]]>
</screen>
</example>
</para>
<para>
プリペアドステートメントごとに、
サーバーのリソースが消費されます。
ステートメントは、使った後はすぐに閉じるべきです。
それを明示的に行わない場合、
ステートメントハンドルが PHP によって開放された後、
ステートメントが閉じられます。
</para>
<para>
プリペアドステートメントが、
ステートメントを実行するもっとも効率が良い方法とは限りません。
プリペアドステートメントが一度しか実行されないと、
クライアントとサーバー間の通信が、
ステートメントを準備しない場合と比べて余計に行われてしまいます。
よって、上の例の <literal>SELECT</literal>
は、プリペアドステートメントを使って実行していません。
</para>
<para>
また、
MySQL の複数INSERT の文法を使うことも検討してみて下さい。
たとえば、複数INSERT によって、
サーバーとクライアント間に必須の通信が、
上に示すプリペアドステートメントの例よりも少なくなります。
</para>
<para>
<example>
<title>複数INSERTを使い、クライアント・サーバー間の通信を減らす</title>
<programlisting role="php">
<![CDATA[
<?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)");
$values = [1, 2, 3, 4];
$stmt = $mysqli->prepare("INSERT INTO test(id) VALUES (?), (?), (?), (?)");
$stmt->bind_param('iiii', ...$values);
$stmt->execute();
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">結果セットの値のデータ型</emphasis>
</para>
<para>
MySQL のクライアントサーバープロトコルは、
プリペアドステートメントと、
準備しないステートメントとでは異なるデータ転送プロトコルを使います。
プリペアドステートメントは、いわゆるバイナリプロトコルを使います。
MySQL は結果セットのデータを、
バイナリフォーマットで "そのまま" 送信します。
結果セットは送信される前は文字列にシリアライズされていません。
クライアントライブラリは、
バイナリデータを受け取って値を適切なPHP のデータ型に変換しようとします。
たとえば、<literal>INT</literal>
として定義されたカラムからの結果は、
PHP の整数値として提供されます。
</para>
<para>
<example>
<title>ネイティブのデータ型</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Non-prepared statement */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer)
label = PHP (string)
]]>
</screen>
</example>
</para>
<para>
この振る舞いが、準備しないステートメントの場合は異なります。
デフォルトでは、準備しないステートメントの結果は、
全て文字列として返されます。
このデフォルトは、接続オプションで変更できます。
接続オプションを使うと、この振る舞いの違いはなくなります。
</para>
<para>
<emphasis role="bold">バインドされた値を使って、結果を取得する</emphasis>
</para>
<para>
プリペアドステートメントからの結果は、
出力値をバインドして取得することもできますし、
<classname>mysqli_result</classname>
から取得するようにリクエストすることもできます。
</para>
<para>
出力変数は、ステートメントの実行後にバインドしなければいけません。
ステートメントの結果セットのそれぞれのカラムごとに、
ひとつの値をバインドしなければいけません。
</para>
<para>
<example>
<title>バインドされた値を出力する</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Non-prepared statement */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$stmt->bind_result($out_id, $out_label);
while ($stmt->fetch()) {
printf("id = %s (%s), label = %s (%s)\n", $out_id, gettype($out_id), $out_label, gettype($out_label));
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer), label = PHP (string)
]]>
</screen>
</example>
</para>
<para>
プリペアドステートメントは、
デフォルトでは、結果セットをバッファリングせずに返します。
ステートメントの結果は、
暗黙のうちに取得されることはありませんし、
サーバーからクライアントに、
バッファリングされるためにデータが送信されることもありません。
結果セットは、全ての結果がクライアントによって取得されるまで、
サーバーのリソースを消費します。
よって、結果は必要に応じて取得することが推奨されます。
クライアントが全ての結果を取得できなかったり、
クライアントが全てのデータを取得する前にステートメントを閉じたりした場合、
<literal>mysqli</literal> は暗黙のうちにデータを取得しなければいけません。
</para>
<para>
プリペアドステートメントであっても、
<methodname>mysqli_stmt::store_result</methodname>
を使って結果をバッファリングすることが可能です。
</para>
<para>
<emphasis role="bold">mysqli_result インターフェイスを使って、結果を取得する</emphasis>
</para>
<para>
バインドされた結果を使う代わりに、
mysqli_result インターフェイスを使って結果を取得することもできます。
<methodname>mysqli_stmt::get_result</methodname>
は、バッファリングされた結果セットを返します。
</para>
<para>
<example>
<title>結果を取得するために、mysqli_result を使う</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Non-prepared statement */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$result = $stmt->get_result();
var_dump($result->fetch_all(MYSQLI_ASSOC));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
[0]=>
array(2) {
["id"]=>
int(1)
["label"]=>
string(3) "PHP"
}
}
]]>
</screen>
</example>
</para>
<para>
<classname>mysqli_result</classname>
インターフェイスを使うと、
クライアント側で結果セットを柔軟に操作することができます。
</para>
<para>
<example>
<title>柔軟に結果を読み取るために、バッファリングされた結果セットを使う</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Non-prepared statement */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP'), (2, 'Java'), (3, 'C++')");
$stmt = $mysqli->prepare("SELECT id, label FROM test");
$stmt->execute();
$result = $stmt->get_result();
for ($row_no = $result->num_rows - 1; $row_no >= 0; $row_no--) {
$result->data_seek($row_no);
var_dump($result->fetch_assoc());
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(2) {
["id"]=>
int(3)
["label"]=>
string(3) "C++"
}
array(2) {
["id"]=>
int(2)
["label"]=>
string(4) "Java"
}
array(2) {
["id"]=>
int(1)
["label"]=>
string(3) "PHP"
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">エスケープと SQL インジェクション</emphasis>
</para>
<para>
バインドされる変数は、
クエリとは別にサーバーに送信されます。
このことが、変数によって邪魔されることはありません。
サーバーはステートメントのテンプレートをパース後、
送信された値を実行時に直接使います。
バインドされたパラメータをエスケープする必要はありません。
サーバーがその値でクエリの文字列を直接置き換えることはないからです。
バインドする変数の型について、サーバーにヒントを提供しなければいけません。
これは、サーバーが適切な変換を行うために必要です。
詳しい情報は、
<methodname>mysqli_stmt::bind_param</methodname> を参照ください。
</para>
<para>
このように、変数とクエリを別に扱うことが、
SQLインジェクションを防ぐ唯一のセキュリティ上の機能だと見なされることがあります。
しかし、全ての値を適切にフォーマットしておけば、
それと同程度のセキュリティを準備しないステートメントでも達成できます。
正しいフォーマットとは、
単純に値をエスケープすることではなく、
それ以上のロジックを含むことに注意すべきです。
よって、この手のデータベースセキュリティに対しては、
プリペアドステートメントが単により便利で、
エラーが起きにくいアプローチになっています。
</para>
<para>
<emphasis role="bold">クライアント側でプリペアドステートメントをエミュレートする</emphasis>
</para>
<para>
API は、クライアント側でのプリペアドステートメントのエミュレートをサポートしていません。
</para>
<para>
<emphasis role="bold">参照</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::prepare</methodname></member>
<member><methodname>mysqli_stmt::prepare</methodname></member>
<member><methodname>mysqli_stmt::execute</methodname></member>
<member><methodname>mysqli_stmt::bind_param</methodname></member>
<member><methodname>mysqli_stmt::bind_result</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.stored-procedures">
<title>ストアドプロシージャ</title>
<para>
MySQL データベースは、ストアドプロシージャをサポートしています。
ストアドプロシージャは、
データベースカタログに保存されたサブルーチンです。
アプリケーションは、ストアドプロシージャを呼び出し、実行できます。
ストアドプロシージャを実行するには、SQL ステートメント
<literal>CALL</literal> を使います。
</para>
<para>
<emphasis role="bold">ストアドプロシージャへの引数</emphasis>
</para>
<para>
MySQL のバージョンによっては、
ストアドプロシージャで <literal>IN</literal>,
<literal>INOUT</literal>, <literal>OUT</literal>
という引数をとることができるものがあります。
mysqli インターフェイスは、
引数の違いについて、特別な考慮を行いません。
</para>
<para>
<emphasis role="bold">IN パラメータ</emphasis>
</para>
<para>
<literal>CALL</literal> ステートメントに渡す入力パラメータに使います。
値が適切にエスケープされていることを必ず確認するようにして下さい。
</para>
<para>
<example>
<title>ストアドプロシージャを呼び出す</title>
<programlisting role="php">
<![CDATA[
<?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)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query("CREATE PROCEDURE p(IN id_val INT) BEGIN INSERT INTO test(id) VALUES(id_val); END;");
$mysqli->query("CALL p(1)");
$result = $mysqli->query("SELECT id FROM test");
var_dump($result->fetch_assoc());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
["id"]=>
string(1) "1"
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">INOUT/OUT パラメータ</emphasis>
</para>
<para>
<literal>INOUT</literal>/<literal>OUT</literal>
パラメータに渡した値は、
セッションの値を使ってアクセスできます
</para>
<para>
<example>
<title>セッションの値を使う</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p(OUT msg VARCHAR(50)) BEGIN SELECT "Hi!" INTO msg; END;');
$mysqli->query("SET @msg = ''");
$mysqli->query("CALL p(@msg)");
$result = $mysqli->query("SELECT @msg as _p_out");
$row = $result->fetch_assoc();
echo $row['_p_out'];
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hi!
]]>
</screen>
</example>
</para>
<para>
アプリケーションやフレームワークの開発者は、
セッションの値やデータベースカタログを調べることによって、
もっと便利なAPIを提供できます。
しかし、カタログを調べることをベースにしたカスタムのやり方は、
パフォーマンスに影響がある可能性があることに注意して下さい。
</para>
<para>
<emphasis role="bold">結果セットを扱う</emphasis>
</para>
<para>
ストアドプロシージャは、
結果セットを返すことができます。
ストアドプロシージャから返された結果セットは、
<methodname>mysqli::query</methodname>
を使っても正しく取得できません。
<methodname>mysqli::query</methodname>
は、ステートメントを実行し、
バッファリングされた結果セットから、存在する場合にだけ、
最初の結果セットを返すものです。
しかし、
<methodname>mysqli::query</methodname> は、
ストアドプロシージャが返す追加の結果セットを隠してしまうため、
ユーザが期待する結果セットを返すことに失敗してしまうのです。
</para>
<para>
ストアドプロシージャから返される結果セットは、
<methodname>mysqli::real_query</methodname>
<methodname>mysqli::multi_query</methodname> を使うと取得できます。
これらの関数は、
<literal>CALL</literal> のような、
任意の数の結果セットを返すステートメントから結果を取得できます。
ストアドプロシージャによって返される、
結果セットが全部取得できない場合は、エラーが発生します。
</para>
<para>
<example>
<title>ストアドプロシージャから、結果を取得する</title>
<programlisting role="php">
<![CDATA[
<?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)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$mysqli->multi_query("CALL p()");
do {
if ($result = $mysqli->store_result()) {
printf("---\n");
var_dump($result->fetch_all());
$result->free();
}
} while ($mysqli->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
---
array(3) {
[0]=>
array(1) {
[0]=>
string(1) "1"
}
[1]=>
array(1) {
[0]=>
string(1) "2"
}
[2]=>
array(1) {
[0]=>
string(1) "3"
}
}
---
array(3) {
[0]=>
array(1) {
[0]=>
string(1) "2"
}
[1]=>
array(1) {
[0]=>
string(1) "3"
}
[2]=>
array(1) {
[0]=>
string(1) "4"
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">プリペアドステートメントを使う</emphasis>
</para>
<para>
上に示した、同じストアドプロシージャから結果を取得する場合に、
プリペアドステートメントを使う場合であっても、
特別な操作は必要ありません。
プリペアドステートメントと、
それを用いないインターフェイスは似ています。
全てのバージョンの MySQL サーバーが、
<literal>CALL</literal>
ステートメントを準備することをサポートしているわけではないことに注意して下さい。
</para>
<para>
<example>
<title>ストアドプロシージャとプリペアドステートメント</title>
<programlisting role="php">
<![CDATA[
<?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)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$stmt = $mysqli->prepare("CALL p()");
$stmt->execute();
do {
if ($result = $stmt->get_result()) {
printf("---\n");
var_dump($result->fetch_all());
$result->free();
}
} while ($stmt->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
---
array(3) {
[0]=>
array(1) {
[0]=>
int(1)
}
[1]=>
array(1) {
[0]=>
int(2)
}
[2]=>
array(1) {
[0]=>
int(3)
}
}
---
array(3) {
[0]=>
array(1) {
[0]=>
int(2)
}
[1]=>
array(1) {
[0]=>
int(3)
}
[2]=>
array(1) {
[0]=>
int(4)
}
}
]]>
</screen>
</example>
</para>
<para>
もちろん、
値を取得するためのバインドAPIもサポートしています。
</para>
<para>
<example>
<title>バインドAPIを使って、プリペアドステートメントとストアドプロシージャを実行する</title>
<programlisting role="php">
<![CDATA[
<?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)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$stmt = $mysqli->prepare("CALL p()");
$stmt->execute();
do {
if ($stmt->store_result()) {
$stmt->bind_result($id_out);
while ($stmt->fetch()) {
echo "id = $id_out\n";
}
}
} while ($stmt->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1
id = 2
id = 3
id = 2
id = 3
id = 4
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">参照</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::next_result</methodname></member>
<member><methodname>mysqli::more_results</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.multiple-statement">
<title>複数のステートメント</title>
<para>
MySQL は、ひとつのステートメントの文字列に、
複数のステートメントを埋め込むことをオプションでサポートしています。
しかし、そうするためには特別な操作が必要です。
</para>
<para>
複数のステートメント、
またはマルチクエリーは、
<methodname>mysqli::multi_query</methodname>
を使って実行する必要があります。
文字列に埋め込まれる個々のステートメントは、
セミコロンで区切ります。
実行されたステートメントによって返される全ての結果セットは、
取得されなければいけません。
</para>
<para>
MySQL サーバーは、
結果セットを返すステートメントと、
返さないステートメントを、
ひとつの複数のステートメントに埋め込むことができます。
</para>
<para>
<example>
<title>複数のステートメント</title>
<programlisting role="php">
<![CDATA[
<?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());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
[0]=>
array(1) {
["_num"]=>
string(1) "0"
}
}
array(1) {
[0]=>
array(1) {
["_num"]=>
string(1) "1"
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">セキュリティ上の考慮</emphasis>
</para>
<para>
<methodname>mysqli::query</methodname>
<methodname>mysqli::real_query</methodname> は、
サーバーで複数のクエリを処理するための接続フラグを設定しません。
複数のステートメントを使うことで、
SQLインジェクション攻撃の被害を軽減するためには、
追加のAPI呼び出しが必要です。
攻撃者は、<literal>; DROP DATABASE mysql</literal>
<literal>; SELECT SLEEP(999)</literal>
のようなステートメントを追加しようとするかもしれません。
攻撃者がステートメントにSQLを追加することに成功したが、
<methodname>mysqli::multi_query</methodname>
がそれを使わなければ、
サーバーは挿入された、有害なSQLステートメントを実行することはありません。
</para>
<para>
<example>
<title>SQLインジェクション</title>
<programlisting role="php">
<![CDATA[
<?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");
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
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
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">プリペアドステートメント</emphasis>
</para>
<para>
プリペアドステートメントを複数のステートメントで使うことは、
サポートされていません。
</para>
<para>
<emphasis role="bold">参照</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::next_result</methodname></member>
<member><methodname>mysqli::more_results</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.transactions">
<title>トランザクションのサポート</title>
<para>
MySQL サーバーは、使っているストレージエンジンによっては、
トランザクションをサポートしています。
MySQL 5.5 以降は、
デフォルトのストレージエンジンは InnoDB です。
InnoDB は、トランザクションの ACID 特性を完全にサポートしています。
</para>
<para>
トランザクションは、
SQL または API を呼び出すことによって制御できます。
<literal>autocommit</literal> モードを有効にしたり、
無効にしたりする目的や、トランザクションをコミットしたり、
ロールバックする目的には、API 呼び出しを使うことを推奨します。
</para>
<para>
<example>
<title><literal>autocommit</literal> モードをSQL と API を使って設定する</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Recommended: using API to control transactional settings */
$mysqli->autocommit(false);
/* Won't be monitored and recognized by the replication and the load balancing plugin */
$mysqli->query('SET AUTOCOMMIT = 0');
]]>
</programlisting>
</example>
</para>
<para>
レプリケーションやロードバランシングプラグインのような、
オプションの機能パッケージによっては、
API の呼び出しを容易に監視できるものもあります。
レプリケーションプラグインは、
トランザクションが API 呼び出しによって制御されている場合に、
それを考慮したロードバランシングを提供します。
トランザクションを考慮したロードバランシングは、
SQL を使って <literal>autocommit</literal>
モードが設定されたり、
コミットやロールバックが行われる場合には利用できません。
</para>
<para>
<example>
<title>コミットとロールバック</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->autocommit(false);
$mysqli->query("INSERT INTO test(id) VALUES (1)");
$mysqli->rollback();
$mysqli->query("INSERT INTO test(id) VALUES (2)");
$mysqli->commit();
]]>
</programlisting>
</example>
</para>
<para>
MySQL サーバーは、
全てのステートメントをロールバックできるわけではないことに注意して下さい。
ステートメントによっては、
暗黙のうちにコミットされるものもあります。
</para>
<para>
<emphasis role="bold">参照</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::autocommit</methodname></member>
<member><methodname>mysqli::begin_transaction</methodname></member>
<member><methodname>mysqli::commit</methodname></member>
<member><methodname>mysqli::rollback</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.metadata">
<title>メタデータ</title>
<para>
MySQL の結果セットは、メタデータを含んでいます。
メタデータは、結果セットで見つかったカラムの情報を説明するものです。
MySQL が送信した全てのメタデータは、
<literal>mysqli</literal> のインターフェイスを通じてアクセスできます。
この拡張モジュールは、受け取った情報に対してまったく変更を行いません。
仮にしたとしても、無視できる程度のものです。
MySQL のサーバーのバージョンによる違いは調整されません。
</para>
<para>
メタデータは、
<classname>mysqli_result</classname>
インターフェイスを使ってアクセスします。
</para>
<para>
<example>
<title>結果セットのメタデータにアクセスする</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = $mysqli->query("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
var_dump($result->fetch_fields());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(2) {
[0]=>
object(stdClass)#3 (13) {
["name"]=>
string(4) "_one"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(1)
["length"]=>
int(1)
["charsetnr"]=>
int(63)
["flags"]=>
int(32897)
["type"]=>
int(8)
["decimals"]=>
int(0)
}
[1]=>
object(stdClass)#4 (13) {
["name"]=>
string(4) "_two"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(5)
["length"]=>
int(5)
["charsetnr"]=>
int(8)
["flags"]=>
int(1)
["type"]=>
int(253)
["decimals"]=>
int(31)
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">プリペアドステートメント</emphasis>
</para>
<para>
プリペアドステートメントを使って作られた結果セットのメタデータは、
アクセス方法も同じです。
<methodname>mysqli_stmt::result_metadata</methodname> が、
適切な <classname>mysqli_result</classname> ハンドルを返してくれます。
</para>
<para>
<example>
<title>プリペアドステートメントのメタデータ</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$stmt = $mysqli->prepare("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
$stmt->execute();
$result = $stmt->result_metadata();
var_dump($result->fetch_fields());
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">参照</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli_result::fetch_fields</methodname></member>
</simplelist>
</para>
</section>
</chapter>