CakePHPのアソシエーション:長所と短所(MySQLで検証)

どうもこんにちは。BriswellのTuanと申します。
前回、PHPメモリに関して色々話しました。

実は、開発時にPHPを使ってゼロからプロジェクトを立ち上げた経験のある方いると思いますが、その経験がなかなか快適ではないと思います。
ソース構造、データマッピング、リクエストハンドリングなどを管理必要があるため、ビジネスロジックを実装するの工数よりかなり多くの工数だと思います。
そのため、主に有効性と汎用性のためにフレームワークを使用します。
Web開発にはさまざまなMVCフレームワークがありますが、CakePHPはおそらく使いやすいと思います。
ただし、CakePHPには他のフレームワークと同じ長所と短所があるため、今回はCakePHPアソシエーションの長所と短所を共有したいと思います。

CakePHPのアソシエーションとは?
CakePHPサイトから下記の定義になります:

アプリケーション中で、異なるオブジェクト同士の関連を定義することは よくあることです。
例えば、記事は多くのコメントを持っていて著者に属しています。 著者は多くの記事とコメントを持っています。
CakePHP はこうしたアソシエーションの管理を 簡単にします。CakePHP には4つのアソシエーションがあります。
hasOne 、 hasMany 、 belongsTo 、そして belongsToMany です。

https://book.cakephp.org/3/ja/orm/associations.html

(サンプルとして、CakePHPサイトからのチュートリアルのデータベースを使います)

今回の話はアソシエーションの長所と短所ですので、実際の使い方を言及しないと思います。興味がある方、上記のリンク直接参照してください。

1ー長所:

①使い方が簡単:
一回テーブルクラスにアソシエーション定義してから、いつでも、どこでも適用できます。

②ソースが分かりやすい(自然言語ちょっと近くと思います)。

<?php
class ArticlesTable extends Table
{
    public function initialize(array $config)
    {
        $this->hasMany('Comments');     // アソシエーション定義
    }
}

(1-1)

<?php
$query = $articles->find('all')->contain(['Comments']);
foreach ($query as $article) {
    echo $article->comments[0]->text;
}

(1-2)

③一括全部必要な情報簡単に取得できます。

上記のソース(1-1, 1-2)を見てから、Articles テーブルの検索操作で、もし Comment のレコードが存在すればそれも取得ことができます。

2ー短所:

①オーバーヘッド
1-2のソースは簡単ですが、実施する時にCakePHPが下記のような SQL を作成して、実行します。

SELECT * FROM articles;
SELECT * FROM comments WHERE article_id IN (1, 2, 3, 4, 5);          // サンプルデータは5つレコードだけ

(2-1)

普通のクエリーと比べて

SELECT * FROM articles LEFT JOIN comments ON comments.article_id = articles.id;

(2-2)

(2-1)に何の問題発生する可能性がありますか?(答えを見ないで1分考えてください。)

問題は「IN (1, 2, 3, 4, 5)」の分です。なぜでしょうか?

サンプルデータは5つレコードだけですが、何十万件の場合はクエリー文は何バイトになりますか? 後、「articles.id」今数型ですが、例えば、idが文字列(10桁)の場合はクエリー文もっと長いでしょうか?

その場合はMySQLのクエリーパケットサイズ(max_allowed_packet)を超える可能性があります。

ただ、CakePHPの作者もそう言うことをもう考えてくれたので、「サブクエリーのストラテジー」と言うオプション追加しました。

SELECT * FROM articles;
SELECT * FROM comments WHERE article_id IN (SELECT id FROM articles);

(2-3)

こ言う書き方は安心ですよね。 しかし、下記の短所になります

②パフォーマンス
(2-1)はメモリ問題発生しなくても、下記のパフォーマンス発生します。

  • クエリー文長くて、インターネットの帯域幅がかかるので通信時間がかかります。
  • クエリーパフォーマンスだと「IN」オペレーターの検索は遅くて、Indexを使えない、Whereでデータを絞り込みますので一番遅いです。

(2-3)は(2-1)よりパフォーマンスが良いですが、Indexも使えないですのでデータ容量が大きくなったら実行時間どんどん長くなります。

3ーまとめて:

処理によって、どちら適用方がいいか考えください。

  • 簡単画面・パフォーマンスあまり影響しない場合はアソシエーションを使う方が楽です。

  • 複雑処理・件数容量が多くの場合はアソシエーションを使うのは遠慮してください。

  • テーブルのカラム数が多くの場合もアソシエーションを使う時にちゃんと、「fields」オプションで、対象のカラムだけ取得してください。 そうしないと、全カラム取得して、また帯域幅がかかりますので。