Zend_Db_Profiler(日本語)

導入

Zend_Db_Profiler を使用すると、クエリの情報を取得できます。 アダプタが実際に実行したクエリの内容や実行所要時間などが取得でき、 余計なデバッグコードをクラスに追加しなくてもクエリを調べられるようになります。 さらに、条件を指定して特定のクエリだけの情報を取得することもできます。

プロファイラを有効にするには、アダプタのコンストラクタで指定するか、 あるいは後からアダプタに指示します。

$params = array(
    'host'     => '127.0.0.1',
    'username' => 'webuser',
    'password' => 'xxxxxxxx',
    'dbname'   => 'test'
    'profiler' => true  // プロファイラを使用します。
                        // false (デフォルト) にすると無効になります。
);

$db = Zend_Db::factory('PDO_MYSQL', $params);

// プロファイラを無効にします
$db->getProfiler()->setEnabled(false);

// プロファイラを有効にします
$db->getProfiler()->setEnabled(true);

profiler オプションの値には柔軟性があり、 その型に応じて、さまざまな形式で解釈されます。 たいていは単純な boolean 値を指定することになるでしょうが、 その他の型を指定することでプロファイラの振る舞いをカスタマイズすることも可能です。

boolean 引数 TRUE を指定すると、 プロファイラを有効にします。あるいは FALSE にすると、プロファイラを無効にします。プロファイラのクラスは、 そのアダプタのデフォルトのプロファイラクラスである Zend_Db_Profiler となります。

$params['profiler'] = true;
$db = Zend_Db::factory('PDO_MYSQL', $params);

プロファイラオブジェクトのインスタンスを、アダプタで使用します。 このオブジェクトの型は、Zend_Db_Profiler あるいはそのサブクラスでなければなりません。 プロファイラを有効にするには、次にようにします。

$profiler = MyProject_Db_Profiler();
$profiler->setEnabled(true);
$params['profiler'] = $profiler;
$db = Zend_Db::factory('PDO_MYSQL', $params);

引数は連想配列で、'enabled'、 'instance' および 'class' のいずれか (あるいはすべて) のキーを持ちます。 'enabled' と 'instance' は、 それぞれ boolean および上で説明したインスタンスです。 'class' は、独自のプロファイラを使用する場合のクラス名を指定します。 このクラスは Zend_Db_Profiler あるいはそのサブクラスでなければなりません。 このクラスは、コンストラクタに何も引数を渡さないでインスタンス化されます。 'class' の内容は、 'instance' を指定した際には無視されます。

$params['profiler'] = array(
    'enabled' => true,
    'class'   => 'MyProject_Db_Profiler'
);
$db = Zend_Db::factory('PDO_MYSQL', $params);

また、引数は Zend_Config のオブジェクトで渡すこともできます。 このオブジェクトのプロパティが、先ほどの連想配列のキーと同じように解釈されます。 たとえば、次のような内容の "config.ini" ファイルがあったとしましょう。

[main]
db.profiler.class   = "MyProject_Db_Profiler"
db.profiler.enabled = true

この設定を適用するには、次のような PHP コードを書きます。

$config = new Zend_Config_Ini('config.ini', 'main');
$params['profiler'] = $config->db->profiler;
$db = Zend_Db::factory('PDO_MYSQL', $params);

'instance' プロパティは、次のように使用します。

$profiler = new MyProject_Db_Profiler();
$profiler->setEnabled(true);
$configData = array(
    'instance' => $profiler
    );
$config = new Zend_Config($configData);
$params['profiler'] = $config;
$db = Zend_Db::factory('PDO_MYSQL', $params);

プロファイラの使用

好きなところでアダプタの getProfiler() メソッドを使用すれば、 プロファイラを取得できます。

$profiler = $db->getProfiler();

これは、Zend_Db_Profiler オブジェクトのインスタンスを返します。 このインスタンスのさまざまなメソッドを使用することで、 クエリの内容を調べることができます。

  • getTotalNumQueries() は、 情報を取得したクエリの総数を返します。

  • getTotalElapsedSecs() は、 情報を取得したクエリの実行所要時間の合計を返します。

  • getQueryProfiles() は、 すべてのクエリ情報を配列で返します。

  • getLastQueryProfile() は、最後に (直近に) 実行されたクエリの情報を (そのクエリが 完了したか否かにかかわらず) 返します (クエリが完了していない場合は、終了時刻が NULL となります)。

  • clear() は、スタック上に残っている 過去のクエリ情報をすべて消去します。

getLastQueryProfile() の返り値、および getQueryProfiles() の個々の要素は Zend_Db_Profiler_Query オブジェクトで、 これを使用すると個々のクエリ自体の情報を調べられます。

  • getQuery() は、クエリの SQL テキストを返します。 パラメータつきのプリペアドステートメントの場合、 クエリがプリペアされた時点のテキストを返します。 つまり、プレースホルダを含んだままの形式ということです。 実行時に置き換えられた値を知ることはできません。

  • getQueryParams() は、 プリペアドクエリを実行する際に使用する、パラメータの値の配列を返します。 ここには、バインドパラメータだけでなく execute() メソッドへの引数も含まれます。 配列のキーは、(1 から始まる) 数値かあるいは (文字列の) パラメータ名となります。

  • getElapsedSecs() は、 クエリの実行所要時間を返します。

Zend_Db_Profiler の提供する情報は、 アプリケーションのボトルネックを調査したり クエリをデバッグしたりする場合に有用です。 例えば、直近に実行されたクエリが実際のところどんなものだったのかを知るには次のようにします。

$query = $profiler->getLastQueryProfile();

echo $query->getQuery();

ページの生成に時間がかかっているとしましょう。この場合、 プロファイラを使用してまず全クエリの実行所要秒数を取得します。 それから、個々のクエリを調べ、一番時間がかかっているのはどれかを見つけます。

$totalTime    = $profiler->getTotalElapsedSecs();
$queryCount   = $profiler->getTotalNumQueries();
$longestTime  = 0;
$longestQuery = null;

foreach ($profiler->getQueryProfiles() as $query) {
    if ($query->getElapsedSecs() > $longestTime) {
        $longestTime  = $query->getElapsedSecs();
        $longestQuery = $query->getQuery();
    }
}

echo '全部で ' . $queryCount . ' 件のクエリが ' . $totalTime .
     ' 秒で実行されました' . "\n";
echo '平均の所要時間: ' . $totalTime / $queryCount . ' 秒' . "\n";
echo '1 秒あたりのクエリ実行数: ' . $queryCount / $totalTime . "\n";
echo '所要時間の最大値: ' . $longestTime . "\n";
echo "一番時間のかかっているクエリ: \n" . $longestQuery . "\n";

プロファイラの高度な使用法

単にクエリを調べるだけでなく、どのクエリを調べるのかという 条件を指定することも可能です。以下で説明するメソッドは、 Zend_Db_Profiler インスタンスのメソッドです。

クエリの実行所要時間による絞り込み

setFilterElapsedSecs() は、クエリの情報を取得する条件として 実行所要時間の最小値を指定します。このフィルタを削除するには、 メソッドに NULL 値を渡します。

// 所要時間が 5 秒以上のクエリのみ調べます
$profiler->setFilterElapsedSecs(5);

// 所要時間にかかわらず、すべてのクエリを調べます
$profiler->setFilterElapsedSecs(null);

クエリの形式による絞り込み

setFilterQueryType() は、クエリの情報を取得する条件として クエリの形式を指定します。複数の形式を扱うには、それらの論理 OR を指定します。 クエリの形式は、Zend_Db_Profiler のクラス定数として以下のように定義されています。

  • Zend_Db_Profiler::CONNECT: 接続操作、あるいはデータベースの選択。

  • Zend_Db_Profiler::QUERY: 他の形式にあてはまらないクエリ。

  • Zend_Db_Profiler::INSERT: 新しいデータをデータベースに追加するクエリ。 一般的には SQLINSERT

  • Zend_Db_Profiler::UPDATE: 既存のデータを更新するクエリ。通常は SQLUPDATE

  • Zend_Db_Profiler::DELETE: 既存のデータを削除するクエリ。通常は SQLDELETE

  • Zend_Db_Profiler::SELECT: 既存のデータを取得するクエリ。通常は SQLSELECT

  • Zend_Db_Profiler::TRANSACTION: トランザクションに関する操作。例えばトランザクションの開始や コミット、ロールバックなど。

既存のフィルタを削除するには、 setFilterElapsedSecs() の引数に NULL だけを渡します。

// SELECT クエリのみを調べます
$profiler->setFilterQueryType(Zend_Db_Profiler::SELECT);

// SELECT、INSERT そして UPDATE クエリを調べます
$profiler->setFilterQueryType(Zend_Db_Profiler::SELECT |
                              Zend_Db_Profiler::INSERT |
                              Zend_Db_Profiler::UPDATE);

// DELETE クエリを調べます
$profiler->setFilterQueryType(Zend_Db_Profiler::DELETE);

// すべてのフィルタを削除します
$profiler->setFilterQueryType(null);

クエリの型を指定することによる情報の取得

setFilterQueryType() を使用すると、 生成される情報を減らすことができます。 すべての情報を保持しておくほうがよい場合もありますが、 普通はそのときに必要な情報のみが見られればよいでしょう。 getQueryProfiles() のもうひとつの機能として、 最初の引数にクエリの型 (あるいは複数の型の論理和) を指定することによるその場での絞り込みが可能です。 クエリの型を表す定数については この節 を参照ください。

// SELECT クエリの情報のみを取得します
$profiles = $profiler->getQueryProfiles(Zend_Db_Profiler::SELECT);

// SELECT、INSERT そして UPDATE クエリの情報のみを取得します
$profiles = $profiler->getQueryProfiles(Zend_Db_Profiler::SELECT |
                                        Zend_Db_Profiler::INSERT |
                                        Zend_Db_Profiler::UPDATE);

// DELETE クエリの情報を取得します
$profiles = $profiler->getQueryProfiles(Zend_Db_Profiler::DELETE);

特化型のプロファイラ

特化型のプロファイラは Zend_Db_Profiler を継承したオブジェクトです。 プロファイリング情報を特別な方法で処理します。

Firebug によるプロファイリング

Zend_Db_Profiler_Firebug は、プロファイリング情報を Firebug コンソール に送信します。

すべてのデータの送信には Zend_Wildfire_Channel_HttpHeaders コンポーネントを使用します。これは HTTP ヘッダを使用するので、 ページのコンテンツには何も影響を及ぼしません。 この方式なら、AJAX リクエストのようにクリーンな JSON および XML レスポンスを要求するリクエストのデバッグも行えます。

要件:

例230 Zend_Controller_Front を使った DB のプロファイリング

// 起動ファイル

$profiler = new Zend_Db_Profiler_Firebug('All DB Queries');
$profiler->setEnabled(true);

// プロファイラを db アダプタにアタッチします
$db->setProfiler($profiler);

// フロントコントローラをディスパッチします

// モデル、ビューそしてコントローラファイル内で発行されたすべての
// DB クエリのプロファイル結果が Firebug に送信されます

例231 Zend_Controller_Front を使わない DB のプロファイリング

$profiler = new Zend_Db_Profiler_Firebug('All DB Queries');
$profiler->setEnabled(true);

// プロファイラを DB アダプタにアタッチします
$db->setProfiler($profiler);

$request  = new Zend_Controller_Request_Http();
$response = new Zend_Controller_Response_Http();
$channel  = Zend_Wildfire_Channel_HttpHeaders::getInstance();
$channel->setRequest($request);
$channel->setResponse($response);

// 出力バッファリングを開始します
ob_start();

// DB クエリを発行すると、それがプロファイリングされます

// データをブラウザに送信します
$channel->flush();
$response->sendHeaders();