Yii2. Особенность ActiveRecord::findOne() Баг или фича?
На этой неделе столкнулись с интересной особенностью работы ActiveRecord::find()->one()
. Как выяснилось, она никак не ограничивает результат выборки, то есть не добавляет к запросу limit(1)
. Чем это плохо и в каких случаях необходимо исправить ваш код? Об этом и поговорим.
Откуда растут ноги?
В первую очередь смутил метод ActiveQuery::one()
. Ниже представлена его реализация:
public function one($db = null) { $row = parent::one($db); if ($row !== false) { $models = $this->populate([$row]); return reset($models) ?: null; } else { return null; } }
Получается, что мы создаем все модели, а потом возвращаем первую? На самом деле нет. В действительности в $rows, а соответственно и в $models всего один элемент, он то и возвращается. Хух, можно спокойно выдохнуть... Нет! Дьявол прячется в деталях. В yii\db\Command::queryInternal
в качестве метода передается fetch
(т.е. одна строка), но запрос выполняется без ограничения количества и в PDO
приходят все данные, что занимает априори больше времени чем тот же запрос с лимитом.
Стоит ли все переписывать?
Методы findOne()
или find()->one()
выполняются, как правило, при поиске по ID
. Если вы используете их именно для этого, то для паники нет причин. Но если вы используете эти методы, например, для получения первой строки выборки, то обязательно добавьте к запросу limit(1)
. Иначе работа вашего приложения может существенно замедлиться.
Итак, алгоритм прост. Если вы уверены, что запрос гарантировано вернет одно значение, то ничего переделывать не стоит, если такой уверенности нет, то вперед к рефакторингу.
P.S. Данная проблема присутствует даже в версии 2.0.7
, а это последняя стабильная версия на момент написания заметки.