Советы PHP-разработчика

Блог содержащий короткие заметки касающиеся программирования для web.

Yii2. Особенность ActiveRecord::findOne() Баг или фича?

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, а это последняя стабильная версия на момент написания заметки.

  • 2016-02-26 16:40:43