PHP 8.5.0 RC 2 available for testing

持続的データベース接続

持続的データベース接続って何?

持続的接続は、スクリプトの実行終了時にも閉じられないリンクです。 持続的接続が要求された時、 PHP は(前もってオープンされたままになっている) 同じ持続的接続が既にオープンされていないかどうかを確認します。 そして、存在する場合には、それを使用します。存在しない場合には、 そのリンクを作成します。'同じ'接続とは、同じホスト、同じユーザー名、 同じパスワード(利用可能な場合)でオープンされた接続のことを意味します。

特定の接続を要求する方法はありませんし、 既存の接続と新しい接続のどちらを取得できるかを保証する方法もありません。 (すべての接続が使用中の場合や、 リクエストが別の接続プールを持つ、 別のワーカーによって処理される場合があります)

つまり、PHP の持続的接続が使えない場合があるということです。 たとえば以下の場合です:

  • 特定のデータベースセッションを、特定のWebユーザーに割り当てる場合
  • 複数のリクエストにまたがる、巨大なトランザクションを生成する場合
  • あるリクエストでクエリを発行し、クエリの結果を別のリクエストで収集する場合

持続的接続は、 持続的でない接続にできない機能は 全く 提供しません。

Webリクエスト

Webサーバーを PHP と統合し、Webページを生成させる方法はふたつあります:

最初は、CGI "ラッパー"としてPHPを使用する方法です。このように実行した場合、 PHP インタプリタのインスタンスは、Webサーバーに(PHPページに関する) ページがリクエストされる度に生成され、破棄されます。 リクエスト毎に破棄されるために、(SQLデータベースサーバーへのリンクのような) 必要な全てのリソースは破棄される際にクローズされます。この場合、 持続的接続を使用することから得るものは何もありません。 持続的接続は持続しないのです。

2番目は、最も一般的ですが、PHP を PHP-FPM か、 マルチプロセスWebサーバー(現在はApacheのみが含まれます)のモジュールとして実行する方法です。 マルチプロセスサーバーは、通常、 実際にWebページを送信する複数のプロセス(子)を管理するプロセス(親)を有しています。 リクエストがクライアントから来ると、 親プロセスは、 他のクライアントにすでに送信を行っていないクライアントの一つに渡します。 このため、 同じクライアントが2番目のリクエストをサーバーに送信した際に最初のではなく他の子プロセスにより送信が行われる可能性があります。 持続的接続がオープンされているとき、 SQL サービスにリクエストを行うそれぞれのページは、 SQL サーバーへの確立された接続を再利用することができます。

注意:

Webリクエスト で使用されているメソッドを確認するには、 phpinfo() の出力における "Server API" の値、 または Webリクエスト から実行した PHP_SAPI の値を確認してください。

サーバーAPI が "Apache 2 Handler" や "FPM/FastCGI" の場合、 持続的接続は同じワーカーで処理されるリクエスト間で使われます。 これら以外の値の場合、 持続的接続はそれぞれのリクエストを処理したあとは持続しません。

コマンドラインのプロセス

コマンドラインから実行する PHP は、 スクリプトごとに新しいプロセスを使います。 持続的接続は、コマンドラインから実行するスクリプト間では共有されません。 よって、cron やコマンドからのような一時的なスクリプトから、 持続的接続を使ってもなんの役にも立ちません。 とはいっても、役に立つ場面はあるかもしれません。 たとえば、たくさんのリクエストや、 個別のタスクがそれぞれのデータベース接続を必要とする多数のタスクを処理する アプリケーションサーバーを書いている場合が挙げられます。

持続的接続をなぜ使うのか?

持続的接続は、SQLサーバーへ接続するオーバーヘッドが大きい場合には有効です。 このオーバーヘッドが実際に大きいがどうかは様々な要因に依存します。 例えば、データベースの種類、 Webサーバーが動作するのと同じコンピューターで動作しているか、 SQLサーバーを動作させているマシンの負荷、等となります。 肝心なのは、接続のオーバーヘッドが高い場合、 持続的接続は著しく効果があるということです。 持続的接続は、 SQLサーバーへの接続を要求するページをリクエスト毎に処理する代わりに、 子プロセスが動作中の間一回しかサーバーへの接続を行わないようにします。 このことは、 持続的接続をオープンしたプロセス毎に、 サーバーへの持続的接続をオープンするということになります。 例えば、 20の異なった子プロセスがSQLサーバーへの持続的接続を行うスクリプトを実行した場合、 各子プロセス毎にSQLサーバーへの20の異なった接続が行われます。

ありうる欠点: 接続数の上限

しかし、データベースへの接続数を制限して使用している場合に、 持続的な子プロセスの接続数がその数を超える場合は、 持続的接続には気をつけたほうが良い欠点がいくつかあります。 もしデータベースの同時接続数の制限が16だとして、 サーバーに多くのアクセスがあったため、 17個の子プロセスが接続しようとするとそのうちの一つは接続に失敗します。 もしスクリプトにコネクションをシャットダウンしないようなバグ(例えば無限ループ)があると、 16程度の同時接続しか許容しないデータベースはすぐにダメになってしまいます。

持続的接続は通常、特定の時点で開く接続数を増加させます。 これはアイドル状態のワーカーが、 以前処理したリクエスト用の接続を保持し続けるためです。 リクエストの急増に対応するために多数のワーカーが起動された場合、 それらのワーカーが開いた接続は、 ワーカーが終了するかデータベースサーバーが接続を閉じるまで残ります。

データベースサーバーが許可する最大接続数が、 Webリクエストワーカーの最大数(それに cronジョブや、 管理接続などのその他の使用分を加えた数)よりも大きいことを確認してください。

放棄された接続や、アイドル状態の接続(タイムアウト)の処理方法について、 データベースのドキュメントで確認してください。 タイムアウトを長く設定すると、 同時に開かれる持続的接続の数が大幅に増加する可能性があります。

ありうる欠点: 接続の状態管理

一部のデータベース拡張モジュールは、 接続が再利用される際にクリーンアップを自動的に実行します。 このクリーンアップのタスクを、 アプリケーション開発者の裁量に委ねる拡張モジュールもあります。 選択したデータベース拡張モジュールとアプリケーション設計によっては、 スクリプト終了前に手動でのクリーンアップが必要になる場合があります。 接続を予期しない状態に陥らせる可能性のある変更には以下が含まれます:

  • データベースの選択 / デフォルトのデータベース
  • テーブルロック
  • 未コミットのトランザクション
  • 一時テーブル
  • プロファイリングのような、特定の設定や機能を有効にした接続

クリーンアップが行われていない、 または閉じられていないテーブルロックやトランザクションは、 他のクエリが無限にブロックされる原因となるほか、 その後の接続の再利用によって予期しない変更が生じる可能性があります。

誤ったデータベースが選択されている場合、 その後の接続の再利用ではクエリが期待通りに実行できなくなります (スキーマが十分に類似している場合、 誤ったデータベース上でクエリが実行される可能性があります)。

一時テーブルがクリーンアップされない場合、 後続のリクエストでは同じテーブルを再作成できません。

クリーンアップは、クラスのデストラクタまたは register_shutdown_function()を使用して実装できます。 また、クリーンアップの機能を組み込んだ、 専用の接続プールプロキシの使用も検討するとよいでしょう。

おわりに

既に述べた持続的接続の振る舞いと、あり得る欠点を考慮すると、 持続的接続を使う場合は、必ず慎重に検討を行うべきです。 持続的接続を使う場合は、 アプリケーションを追加で変更し、 データベースサーバーとWebサーバーおよび/またはPHP-FPMを慎重に設定すべきです。

サーバーへ接続するオーバーヘッドの原因を調査・修正する別の代替案 (例:データベースサーバーへのDNS逆引きの無効化)や、 専用の接続プールプロキシを検討してください。

高トラフィックのWeb APIについては、代替のランタイムや、 長時間実行可能なアプリケーションサーバーの使用を検討してください。

add a note

User Contributed Notes 7 notes

up
22
Tom
15 years ago
There's a third case for PHP: run on a fastCGI interface. In this case, PHP processes are NOT destroyed after each request, and so persistent connections do persist. Set PHP_FCGI_CHILDREN << mysql's max_connections and you'll be fine.
up
14
ambrish at php dot net
15 years ago
In IBM_DB2 extension v1.9.0 or later performs a transaction rollback on persistent connections at the end of a request, thus ending the transaction. This prevents the transaction block from carrying over to the next request which uses that connection if script execution ends before the transaction block does.
up
14
php at alfadog dot net
11 years ago
One additional not regarding odbc_pconnect and possibly other variations of pconnect:

If the connection encounters an error (bad SQL, incorrect request, etc), that error will return with be present in odbc_errormsg for every subsequent action on that connection, even if subsequent actions don't cause another error.

For example:

A script connects with odbc_pconnect.
The connection is created on it's first use.
The script calls a query "Select * FROM Table1".
Table1 doesn't exist and odbc_errormsg contains that error.

Later(days, perhaps), a different script is called using the same parameters to odbc_pconnect.
The connection already exists, to it is reused.
The script calls a query "Select * FROM Table0".
The query runs fine, but odbc_errormsg still returns the error about Table1 not existing.

I'm not seeing a way to clear that error using odbc_ functions, so keep your eyes open for this gotcha or use odbc_connect instead.
up
14
christopher dot jones at oracle dot com
18 years ago
For the oci8 extension it is not true that " [...] when using transactions, a transaction block will also carry over to the next script which uses that connection if script execution ends before the transaction block does.". The oci8 extension does a rollback at the end scripts using persistent connections, thus ending the transaction. The rollback also releases locks. However any ALTER SESSION command (e.g. changing the date format) on a persistent connection will be retained over to the next script.
up
11
ynzhang from lakeheadu canada
16 years ago
It seems that using pg_pconnect() will not persist the temporary views/tables. So if you are trying to create temporary views/tables with the query results and then access them with the next script of the same session, you are out of luck. Those temporary view/tables are gone after each PHP script ended. One way to get around this problem is to create real view/table with session ID as part of the name and record the name&creation time in a common table. Have a garbage collection script to drop the view/table who's session is expired.
up
10
jean_christian at myrealbox dot com
23 years ago
If anyone ever wonders why the number of idle db process (open connections) seems to grow even though you are using persistent connections, here's why:

"You are probably using a multi-process web server such as Apache. Since
database connections cannot be shared among different processes a new
one is created if the request happen to come to a different web server
child process."
up
8
andy at paradigm-reborn dot com
18 years ago
To those using MySQL and finding a lot of leftover sleeping processes, take a look at MySQL's wait_timeout directive. By default it is set to 8 hours, but almost any decent production server will have been lowered to the 60 second range. Even on my testing server, I was having problems with too many connections from leftover persistent connections.
To Top