recursive設定によるfind()性能改善
CakePHPでは、モデルにアソシエーションを設定している場合、recursive(=>joinする階層)はデフォルトで0に設定されています。「recursiveゼロ」の意味するところとは、「1跨ぎまでのJoinを行う」ということになり、Find()関数を使用した場合、デフォルトの状態でJoinが行われてしまいます。テーブル単体でfindしたい場合などは、find()の前に$recursive = -1とすることで余計なjoin処理を省略することができます。join先のテーブルのレコード数が多い場合などは効果絶大。
例
Userとそれに紐付くUsercommentモデルを用意しました。
-- usersテーブル CREATE TABLE IF NOT EXISTS `users` ( `id` INT NOT NULL AUTO_INCREMENT , `username` VARCHAR(50) NOT NULL , PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_general_ci
-- usercommentsテーブル CREATE TABLE IF NOT EXISTS `usercomments` ( `id` INT NOT NULL , `user_id` INT NULL , `commment` TEXT NULL , PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_general_ci
アソシエーションの設定。user(ユーザ)がいて、それに紐付くusercomment(ユーザのコメント)
// Userモデル class User extends AppModel { var $name = 'User'; var $hasMany = array( 'Usercomment' => array( 'className' => 'Usercomment', 'foreignKey' => 'user_id' // 省略可 ), ); }
実行SQLをログ出力
まず、わかりやすくするためSQLコマンドをCakeログに出力しましょう。(別でSQLログを参照できる場合はそれを確認していただければいいと思いますが、アプリケーションログに出力することで時系列としてわかりやすくなるので個人的にはこちらを採用しています。)
function logQueryに以下を追加。ここを参照にさせていただきました。
// cake/libs/model/datasources/dbo_source.php $logMessage = 'queryLog = '.print_r( $this->_queriesLog[count( $this->_queriesLog) - 1], true); if ($this->error) { $this->log( $logMessage, LOG_ERROR); } else { $this->log( $logMessage, LOG_DEBUG); }
$recursive = 0 でfind
findのみの場合、User(usersテーブル)へのselectと、それに対するUsercommment (usercommentsテーブル)へのjoinが行われます。
// コード $this->data = $this->User->find( 'all');
-- SQLログ queryLog = Array ( [query] => SELECT `User`.`id`, `User`.`username`, `User`.`password`, `User`.`status`, `User`.`created`, `User`.`modified` FROM `users` AS `User` WHERE 1 = 1 [error] => [affected] => 4 [numRows] => 4 [took] => 1 ) queryLog = Array ( [query] => SELECT `Usercomment`.`id`, `Usercomment`.`user_id`, `Usercomment`.`commment`, `Usercomment`.`status`, `Usercomment`.`created`, `Usercomment`.`modified` FROM `usercomments` AS `Usercomment` WHERE `Usercomment`.`user_id` IN (1, 2, 3, 4) [error] => [affected] => 0 [numRows] => 0 [took] => 1 )
$recursive = -1 でfind
findの前にrecursive = -1を設定すると、User(usersテーブル)へのselectのみが行われるようになります。
// コード $this->User->recursive = -1; $this->data = $this->User->find( 'all');
-- SQLログ queryLog = Array ( [query] => SELECT `User`.`id`, `User`.`username`, `User`.`password`, `User`.`status`, `User`.`created`, `User`.`modified` FROM `users` AS `User` WHERE 1 = 1 [error] => [affected] => 4 [numRows] => 4 [took] => 1 )
設定箇所
考え方次第ですが、代表的には以下の2つが考えられると思います。
1) app/Model/AppModel.phpに設定して、デフォルトを$recursive = -1にする。
この場合、joinが必要な場合はfindを行う際に、$this->[Model]->recursive = 0;などを追加します。
class AppModel extends Model { public $recursive = -1; }
2) 各々のfind処理の前に$recursive = -1を毎度設定する。今回試した例です。
個人的には、2を選択しています。
SQLのログは試験中は出力しておいて、余計な呼び出しは省略していくというのは、特にフレームワークを使用する際は注視して使用すると、もっと使い勝手がよくなると思います。もちろん、本番向けは取り除いて下さい。性能劣化につながりますので。。
関連記事
-
フィールド単位でバリデーション無効化
save処理の際に条件付でモデルのバリデーションをフィールド単位でスキップする処理のメモです。(最終的に使うことはなかったのでメモ) 関数は unset( $this->->validate[
-
Textヘルパー
Textヘルパーには、テキスト処理に関する便利な機能があります。リンク付与やテキストの抜粋・ハイライトや切り取り処理など。ソース:/cake/libs/view/helpers/text.php
-
Jsヘルパーを使用してAjax更新
更新処理でページ遷移を伴う場合、ページ全体をレスポンスするのに対して、Ajax処理ではページの一部のレスポンスが可能となるためサーバからの通信量を抑えることが可能となります。 Jsヘルパーを使用して
-
メール送信(qdmail)
CakePHP(+qdmail)でメール送信する処理を実装しました。 準備 2つのライブラリ(.php)をダウンロードし、以下のように配置して下さい。 app/controllers/compo
-
ユーザ登録(仮登録・メール・本登録)
ここ最近は、メールアドレスだけでなく、SNSのアカウントと連携してユーザ登録することもできるWebサービスが増えてきましたね。ユーザは割合としてどちらを選んでるのか気になるところですが。私はできるだけ
-
WYSIWYGエディタを実装
WYSIWYGエディタをCakePHPにいくつか試してみました。 CKEditor 実装 // head // View(~.ctp) 解凍してwebroot/jsフ
-
Secutiryユーティリティ
CakePHPでは、データのハッシュ化もしくは暗号化のためのメソッドSecurityユーティリティが用意されています。 ソース: /cake/libs/security.php Security:
-
独自のバリデーションルール
CakePHPで組み込みバリデーションをつくる方法は色々あります。 参考:Data Validation — CakePHP Cookbook v1.3 documentation 上記リンク内に
-
コントローラ内でバリデーション処理を呼び出す
通常、saveメソッドの際にバリデーション処理も自動で行われますが、save処理と切り離してバリデーションを行うこともできます。このときは、save時と若干異なる処理体系になります。 バリデーシ
-
ビルトインされたバリデーションルール
CakePHP標準で多くのバリデーションルールが搭載されています。 ソース:/cake/libs/validation.php email / maxLength / minLength /