Containableは便利だけど余計なSQLを発行しているのが気になっています。
こういうモデルがあってですね。
<?php class Order extends AppModel { var $name = 'Order'; var $actsAs = array('Containable'); var $belongsTo = array('User'); var $hasMany = array('OrderDetail'); } class User extends AppModel { var $name = 'User'; var $actsAs = array('Containable'); } class OrderDetail extends AppModel { var $name = 'OrderDetail'; var $actsAs = array('Containable'); var $belongsTo = array('Item'); } class Item extends AppModel { var $name = "Item"; var $actsAs = array('Containable'); var $belongsTo = array('Category'); }
Order->OrderDetail(hasMany)->Item(belongsTo)->Category(belongsTo)という階層構造。
Order->Userは純粋に1:1。
で、注文IDから明細まで一気に持ってきたいのでこういうコードを書いてみた。
<?php $this->Order->recursive = 2; $result = $this->Order->read(null,$orderId);
実行されたSQLダンプがこれ。わかりやすくカラムはアスタリスクにした。
Nr | Query | Error | Affected | Num. rows | Took (ms) |
---|---|---|---|---|---|
1 | SHOW FULL COLUMNS FROM `orders` | 12 | 12 | 1 | |
2 | SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= 'utf8_general_ci'; | 1 | 1 | 0 | |
3 | SHOW FULL COLUMNS FROM `users` | 20 | 20 | 2 | |
4 | SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= 'utf8_unicode_ci'; | 1 | 1 | 1 | |
5 | SHOW FULL COLUMNS FROM `order_details` | 6 | 8 | 1 | |
6 | SHOW FULL COLUMNS FROM `items` | 11 | 11 | 8 | |
7 | SHOW FULL COLUMNS FROM `categories` | 5 | 5 | 1 | |
8 | SELECT * FROM `orders` AS `Order` LEFT JOIN `users` AS `User` ON (`Order`.`user_id` = `User`.`id`) WHERE `Order`.`id` = 5 LIMIT 1 | 1 | 1 | 1 | |
9 | SELECT * FROM `users` AS `User` WHERE `User`.`id` = 1 | 1 | 1 | 0 | |
10 | SELECT * FROM `order_details` AS `OrderDetail` WHERE `OrderDetail`.`order_id` = (5) | 1 | 1 | 0 | |
11 | SELECT * FROM `items` AS `Item` WHERE `Item`.`id` = 1282 | 1 | 1 | 0 |
Cakeに頼らずmodel#Queryを使えば、このSQL一発で用が足りる。
select * from orders as o inner join users as u on o.user_id = u.id inner join order_details as od on o.id = od.order_id inner join items as i on od.item_id = i.id where o.id = ?
このコードのSQL実行ダンプはこちら。
Nr | Query | Error | Affected | Num. rows | Took (ms) |
---|---|---|---|---|---|
1 | SHOW FULL COLUMNS FROM `orders` | 12 | 12 | 1 | |
2 | SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= 'utf8_general_ci'; | 1 | 1 | 0 | |
3 | SHOW FULL COLUMNS FROM `users` | 20 | 20 | 2 | |
4 | SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= 'utf8_unicode_ci'; | 1 | 1 | 0 | |
5 | SHOW FULL COLUMNS FROM `order_details` | 8 | 8 | 1 | |
6 | SHOW FULL COLUMNS FROM `items` | 11 | 11 | 1 | |
7 | SHOW FULL COLUMNS FROM `categories` | 5 | 5 | 1 | |
8 | select '*'
from orders as o inner join users as u on o.user_id = u.id inner join order_details as od on o.id = od.order_id inner join items as i on od.item_id = i.id where o.id = '5' | 1 | 1 | 1 |
見ての通り、recursiveが2の時の子供テーブル(Item)のFULL COLUMNで8msもかかっている。でもこれDebugを0にすれば/tmp/cache/modelから持ってくるから恐らくそんなにかからないと思われる。
CakeはhasManyの場合joinしないで、親でまず単発でSQLを流す。次に子供を親のIDでひっぱる。そんで結果をマージして、モデル名の連想配列に返してくれる。joinすれば一発じゃねって思ったんだけど、パフォーマンス的にたいした問題では今の所は無いしなー。ソーシャルゲームだとjoinlessな仕組みが最も合理的らしいんだけど、このsqldump見てもそのワケが窺い知れる。
ここまで書いてみて、結論としてはContainableに頼っていいんじゃねってことに落ち着きまんた。