クエリがプリフェッチする行数を設定する

# oci_set_prefetch

(PHP 5, PHP 7, PHP 8, PECL OCI8 >= 1.1.0)

oci_set_prefetch — クエリがプリフェッチする行数を設定する

### 説明

function oci_set_prefetch(resource `$statement`, int `$rows`): bool

oci_execute() のコール、およびそれに伴う内部的なデータベースへのリクエストがすべて成功した後に Oracle Client ライブラリがバッファに取り込む行数を設定します。 クエリが返す結果の行数が多くなる場合は、プリフェッチ行数をデフォルトの oci8.default_prefetch より大きくしておくとパフォーマンスが劇的に向上します。

プリフェッチは、Oracle がデータベースからデータを取得する際の効率的な方法で、 一回のネットワークリクエストで複数の行を取得します。こうすれば、 ネットワークや CPU の利用効率がよくなります。 行のバッファリングは OCI8 の内部的なものであり、 プリフェッチ行数が変わっても OCI8 のフェッチ関数群の挙動は変わりません。 たとえば、oci_fetch_row() は、常に一行だけを返します。 プリフェッチバッファはステートメント単位で設定されるものであり、 ステートメントを再実行したときや他の接続から実行したときには使われません。

oci_set_prefetch() は oci_execute() の前にコールします。

ネットワークやデータベースの状況を考慮して、 プリフェッチの値を適切な大きさに設定することがチューニングの目標です。 大量の行を返すクエリの場合は、データベースからの行の取得を いくつかの塊に分けて行うとシステム全体の効率が良くなるかもしれません (つまり、プリフェッチの値を行数より小さめに設定するということです)。 こうすれば、PHP スクリプトが現在の行を処理している間にも データベースが別のユーザーからのステートメントを処理できるようになります。

クエリのプリフェッチが導入されたのは、Oracle 8i からです。 REF CURSOR のプリフェッチが導入されたのは Oracle 11gR2 からで、 PHP を Oracle 11gR2 以降のクライアントライブラリとリンクすれば使えます。 ネストしたカーソルのプリフェッチが導入されたのは Oracle 11gR2 からで、 これを使うには Oracle クライアントライブラリと接続先のデータベースがどちらも 11gR2 以降でなければなりません。

プリフェッチは、LONG あるいは LOB 列を含むクエリには対応していません。 プリフェッチに対応していないクエリの場合はプリフェッチの値は無視され、 どんな場合でも一行単位でフェッチします。

Oracle Database 12c では、PHP で設定したプリフェッチの値を Oracle のクライアント設定ファイル `oraaccess.xml` で上書きできます。 詳細は、Oracle のドキュメントを参照ください。

### パラメータ

`statement`
  oci_parse() で作成して oci_execute() で実行した有効な OCI8 ステートメント ID、 あるいは `REF CURSOR` ステートメント ID。
`rows`

  プリフェッチする行数。>= 0

### 戻り値

成功した場合に `true` を、失敗した場合に `false` を返します。

### 例

例1 クエリのデフォルトプリフェッチ値の変更

    <?php

    $conn = oci_connect('hr', 'welcome', 'localhost/XE');

    $stid = oci_parse($conn, 'SELECT * FROM myverybigtable');
    oci_set_prefetch($stid, 300);  // oci_execute() のコール前に設定します
    oci_execute($stid);

    echo "<table border='1'>\n";
    while ($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) {
        echo "<tr>\n";
        foreach ($row as $item) {
            echo "    <td>".($item !== null ? htmlentities($item, ENT_QUOTES) : "&nbsp;")."</td>\n";
        }
        echo "</tr>\n";
    }
    echo "</table>\n";

    oci_free_statement($stid);
    oci_close($conn);

    ?>

例2 REF CURSOR のデフォルトプリフェッチの変更

    <?php
    /*
      下記のようにして PL/SQL ストアド・プロシジャーを作成します。

      CREATE OR REPLACE PROCEDURE myproc(p1 OUT SYS_REFCURSOR) AS
      BEGIN
        OPEN p1 FOR SELECT * FROM all_objects WHERE ROWNUM < 5000;
      END;
    */

    $conn = oci_connect('hr', 'welcome', 'localhost/XE');

    $stid = oci_parse($conn, 'BEGIN myproc(:rc); END;');
    $refcur = oci_new_cursor($conn);
    oci_bind_by_name($stid, ':rc', $refcur, -1, OCI_B_CURSOR);
    oci_execute($stid);

    // プリフェッチはカーソルの実行前に変更します。
    // REF CURSOR のプリフェッチが使えるのは、PHP を Oracle 11R2 以降のクライアントライブラリとリンクしたときです。
    oci_set_prefetch($refcur, 200);
    oci_execute($refcur);

    echo "<table border='1'>\n";
    while ($row = oci_fetch_array($refcur, OCI_ASSOC+OCI_RETURN_NULLS)) {
        echo "<tr>\n";
        foreach ($row as $item) {
            echo "    <td>".($item !== null ? htmlentities($item, ENT_QUOTES) : "&nbsp;")."</td>\n";
        }
        echo "</tr>\n";
    }
    echo "</table>\n";

    oci_free_statement($refcur);
    oci_free_statement($stid);
    oci_close($conn);

    ?>

PHP OCI8 で REF CURSOR からフェッチした内容を別の PL/SQL プロシージャに渡すときには、REF CURSOR のプリフェッチ行数を `0` に設定して行が結果セットから "失われる" ことを防ぎます。 プリフェッチの値は OCI8 内部でのデータベースへのリクエスト時に余計に取得する行数です。 つまり、`0` に設定すると一度に一行だけ取得するということになります。

例3 REF CURSOR を Oracle に戻すときのプリフェッチの設定

    <?php

    $conn = oci_connect('hr', 'welcome', 'localhost/orcl');

    // REF CURSOR を取得します
    $stid = oci_parse($conn, 'BEGIN myproc(:rc_out); END;');
    $refcur = oci_new_cursor($conn);
    oci_bind_by_name($stid, ':rc_out', $refcur, -1, OCI_B_CURSOR);
    oci_execute($stid);

    // ふたつの行を表示しますが、それ以上の余計な行はプリフェッチしません。
    // 余計な行は myproc_use_rc() には戻されません。
    oci_set_prefetch($refcur, 0);
    oci_execute($refcur);
    $row = oci_fetch_array($refcur);
    var_dump($row);
    $row = oci_fetch_array($refcur);
    var_dump($row);

    // REF CURSOR を myproc_use_rc() に渡して、
    // 結果セットに対してさらにデータ処理を行います
    $stid = oci_parse($conn, 'begin myproc_use_rc(:rc_in); end;');
    oci_bind_by_name($stid, ':rc_in', $refcur, -1, OCI_B_CURSOR);
    oci_execute($stid);

    ?>

### 参考

- oci8.default_prefetch ini オプション

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open