PHP 8.4.2 Released!

oci_set_prefetch

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

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

説明

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);

?>

参考

add a note

User Contributed Notes 1 note

up
1
bmichael at goldparrot dot com
21 years ago
If your are using Oracle's OCI libraries, on any project, which PHP does, you can use this limit.

I have done network level testing on the effect of this parameter. It does improved efficiency. Big Time.

Oracle uses SQL*Net as the transport mechanism for data between your connection and the database. That is why you must setup Oracle properly.

This parameter tells SQL*NET to Buffer more results. When SQL*NET (at the server) gets a request for data, it bundles up X rows (1,2,3,1000, etc) for transport. It sends the appropriate SQL*NET headers back to the client, Waits for an ACK then begins sending data in MTU sized chunks (ethernet is something like 1500 bytes and ATM's WANS are around 1000 bytes). The chunk size can also be tuned in SQL*NET, but with much less improvements.

TCP/IP then takes the data across the wire, breaking it up into multiple TCP/IP packets.

Once the exchange is done, the SQL*NET client sends an
ACK back to the SQL*NET Listener (the Oracle Server) and the transaction is complete.

Each round trip, SQL*NET looks up inside the server memory (UGA - user global area) to find the query results. It then grabs the rows necessary to send. If it is one row, versus 1000 rows. The process is the same.

There is much I could tell you on how the Database itself reacts. If you can significantly lessen the amount of round trips you are making... WOW.

For more info on Oracle OCI go to http://otn.oracle.com
To Top