SQL query opmitization for HABTM in CakePHP
My last SQL optimization post found no response from the CakePHP community; which makes me wonder how many are actually using ACL with large databases. Anyhow, the ticket was submitted and gwoo seemed to approve the results. I can also now confirm that the last suggested indexes will give another push to your performance:
ARO: alias ACO: model and foreign_key
Now what’s that about HABTM? While reading the source code for the Model class, I stumbled on the Model::_deleteLinks() method:
function _deleteLinks($id) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
foreach ($this->hasAndBelongsToMany as $assoc => $data) {
if (isset($data['with'])) {
$model =& $this->{$data['with']};
$records = $model->findAll(array($data['foreignKey'] => $id), $model->primaryKey, null, null, null, -1);
if (!empty($records)) {
foreach ($records as $record) {
$model->delete($record[$model->name][$model->primaryKey]);
}
}
} else {
$table = $db->name($db->fullTableName($data['joinTable']));
$conditions = $db->name($data['foreignKey']) . ' = ' . $db->value($id);
$db->query("DELETE FROM {$table} WHERE {$conditions}");
}
}
}
If you look carefully, you will notice a useless loop in the case your HABTM has the ‘with’ key set. That means that for every association record there is for the foreignKey, an SQL query will be committed - some food for thought…
I came up with a quick alternative that still hasn’t been really tested but it should work:
function _deleteLinks($id) {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
foreach ($this->hasAndBelongsToMany as $assoc => $data) {
if (isset($data['with'])) {
$model =& $this->{$data['with']};
$table = $model->useTable;
$conditions = $db->name($data['foreignKey']) . ' = ' . $id;
} else {
$table = $db->name($db->fullTableName($data['joinTable']));
$conditions = $db->name($data['foreignKey']) . ' = ' . $db->value($id);
}
$db->query("DELETE FROM {$table} WHERE {$conditions}");
}
}
I also asked gwoo again before making a fool of myself and he suggested it be submitted as an enhancement.
That’s it for now, thought I’d share that - I believe you’d want to make the changes until they are implemented in the core (if you care about performance that is).
Trackbacks
Use this link to trackback from your own site.


Hi,
It’s good to see tweaks being suggested (as tickets); if they have diffs/patches and a test case they are likely to get looked at a lot quicker than without.
I wonder if the reliance in this method on query could cause problems with not-mySql dbs, it’s quite a simple query so maybe not but …. test cases allow such ponderings to be answered quite quickly
keep up the (myway? Ok a diff with a side of test-case please) patchin’,
cheers,
AD
AD7Six: You’re totally right about the tests. Too much work right now to write one, so I thought I should at least share it - maybe someone else can write the test if he/she is concerned with performance? hint, hint
TBH, having to be portable across different DB engines slipped my mind at the time of writing; but, with my limited experience I believe it shouldn’t cause any problem. Hopefully I’ll have some kind of test when I’ll be wrapping the app, - until then, too much work to worry about.
Oh, before I forget, thanks for dropping by; and since I never had the chance to comment on your blog, just wanted to say cheers for all the bits I gathered there.
Jad