読者です 読者をやめる 読者になる 読者になる

Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

Containable Behaviourのちょっと気になる所。

CakePHP

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ダンプがこれ。わかりやすくカラムはアスタリスクにした。

(default) 11 queries took 16 ms
NrQueryErrorAffectedNum. rowsTook (ms)
1SHOW FULL COLUMNS FROM `orders`12121
2SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= 'utf8_general_ci';110
3SHOW FULL COLUMNS FROM `users`20202
4SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= 'utf8_unicode_ci';111
5SHOW FULL COLUMNS FROM `order_details`681
6SHOW FULL COLUMNS FROM `items`11118
7SHOW FULL COLUMNS FROM `categories`551
8SELECT * FROM `orders` AS `Order` LEFT JOIN `users` AS `User` ON (`Order`.`user_id` = `User`.`id`) WHERE `Order`.`id` = 5 LIMIT 1111
9SELECT * FROM `users` AS `User` WHERE `User`.`id` = 1 110
10SELECT * FROM `order_details` AS `OrderDetail` WHERE `OrderDetail`.`order_id` = (5) 110
11SELECT * FROM `items` AS `Item` WHERE `Item`.`id` = 1282 110

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実行ダンプはこちら。

(default) 8 queries took 7 ms
NrQueryErrorAffectedNum. rowsTook (ms)
1SHOW FULL COLUMNS FROM `orders`12121
2SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= 'utf8_general_ci';110
3SHOW FULL COLUMNS FROM `users`20202
4SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE COLLATION_NAME= 'utf8_unicode_ci';110
5SHOW FULL COLUMNS FROM `order_details`881
6SHOW FULL COLUMNS FROM `items`11111
7SHOW FULL COLUMNS FROM `categories`551
8select '*'

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'

111

見ての通り、recursiveが2の時の子供テーブル(Item)のFULL COLUMNで8msもかかっている。でもこれDebugを0にすれば/tmp/cache/modelから持ってくるから恐らくそんなにかからないと思われる。

CakeはhasManyの場合joinしないで、親でまず単発でSQLを流す。次に子供を親のIDでひっぱる。そんで結果をマージして、モデル名の連想配列に返してくれる。joinすれば一発じゃねって思ったんだけど、パフォーマンス的にたいした問題では今の所は無いしなー。ソーシャルゲームだとjoinlessな仕組みが最も合理的らしいんだけど、このsqldump見てもそのワケが窺い知れる。

ここまで書いてみて、結論としてはContainableに頼っていいんじゃねってことに落ち着きまんた。