【CakePHP2.x】ModelのcounterCacheは、負荷が高い上に、複数キーで結合するテーブルに適用できない
counterCacheは、複数キーで結合するテーブルに適用できない
CakePHPのcounterCacheの便利さに甘えてカウントを保存しようとしたところ、うまくいかない事案が発生。
今回の対象のModelは複数キーでの紐付けが必要でした。
イメージとしてはこのような感じです。
<?php App::uses('AppModel', 'Model'); class Sample1Model extends Model { public $belongsTo = array( 'Sample2' => array( 'foreignKey' => false, 'conditions' => array( 'Sample1.key1 = Sample2.key1', 'Sample1.key2 = Sample2.key2', ), 'counterCache' => 'sample1_count', 'counterScope' => array( 'Sample1.scope1' => true, 'Sample1.scope2' => true, ), ) ); }
よくある、foreignKey = false
とconditions
を使って結合したパターンです。
ですがこれ、saveやdelete時にWarningが発生して、カウント処理も動きません。
Warning (2): array_key_exists(): The first argument should be either a string or an integer [CORE/Cake/Model/Model.php, line 2162] Warning (2): array_key_exists() [function.array-key-exists]: The first argument should be either a string or an integer [CORE/Cake/Model/Model.php, line 2162]
lib/Cake/Model/Model.php
のupdateCounterCache
メソッドの処理を追うとわかりますが、結論としてはforeignKeyを元に更新を行うため、文字列でないfalse
で怒られる上に、更新するレコードを認識できないという理由でした。
一応、foreignKeyを空白文字列にすれば、Warningは回避できますが、カウントできないという結果は同じです。
counterCacheは、負荷が高い
勝手な思い込みで、counterCacheはModelの更新内容に合わせて、countの増減(プラス1 or マイナス1)をしてくれるものだと思っていました。
しかし、上記の件でソースを追ったところ、毎回全レコードからカウントクエリで取りなおすという、言ってみればシンプルなコードになっていました。
レコード数が少ない場合はズレもなくていいのですが、数百万、億といったレコード数だったり、合わせて更新の頻度が多い場合は到底耐えられるものではありません。
RDBの場合は素直にトリガを利用するのが安心かもしれません。