Когда ваше приложение на Yii2 начинает тормозить, первое, что приходит в голову — "может, сервер слабый?" Или "пора покупать больше памяти". Но часто проблема не в железе, а в том, как вы общаетесь с базой данных. Если каждый запрос тянет лишние данные или делает 100 мелких запросов вместо одного большого — вот вам и тормоза. В этой статье я покажу, как с помощью ActiveQuery — встроенного инструмента Yii2 — сделать запросы быстрыми и элегантными. Вы научитесь не просто писать код, а думать о том, что происходит под капотом.

Digital-студия WNDER


Ссылка на оригинальную статью

Почему ActiveQuery — это не просто "еще один ORM"

ActiveQuery — это строитель запросов, который работает в паре с Active Record. Он позволяет вам писать запросы в объектно-ориентированном стиле, а не строками SQL. Но главное его преимущество — гибкость. Вы можете строить запросы по частям, переиспользовать их, добавлять условия в зависимости от логики приложения. Это как конструктор LEGO: из маленьких деталей собираете большой механизм.

Но с большой силой приходит большая ответственность. Многие разработчики используют ActiveQuery как простую обертку над SQL, не задумываясь о производительности. А зря. Вот основные проблемы, которые я вижу в проектах:

  • Загрузка всех полей таблицы, когда нужны только два-три.
  • Ленивая загрузка связанных данных, которая порождает N+1 запросов.
  • Отсутствие индексов на колонках, по которым часто фильтруют.
  • Игнорирование кеширования результатов.

Давайте разберем каждую проблему и способы их решения.

Выбираем только нужные поля: не тащим лишнее

Представьте, что вы заказываете пиццу. Вы просите одну порцию, а вам привозят целый ящик — половину вы выбросите. Так и с запросами: если вам нужны только имена пользователей, зачем тащить из базы их пароли, email, даты рождения? Это лишний трафик, лишняя память.

В ActiveQuery для этого есть метод select(). Вы можете указать, какие именно поля вам нужны. Например:

$users = User::find()
    ->select(['id', 'username', 'email'])
    ->where(['active' => 1])
    ->all();

Этот запрос вернет только три поля. Но есть нюанс: если вы используете Active Record, то после такого селекта объекты будут "частично загружены". Это значит, что обращение к незагруженному полю вызовет дополнительный запрос. Поэтому будьте внимательны.

Если вам нужны только данные для отображения, а не для изменения, используйте asArray(). Тогда вы получите обычные массивы, а не объекты. Это быстрее и экономит память:

$users = User::find()
    ->select(['id', 'username'])
    ->asArray()
    ->all();
Лайфхак: Если вам нужны только одно или два поля, можно использовать column() — он вернет плоский массив значений. Например, список ID всех активных пользователей: User::find()->where(['active' => 1])->column().

Жадная загрузка против ленивой: убиваем проблему N+1

Это, пожалуй, самая частая причина медленных страниц. Проблема N+1 возникает, когда вы в цикле обращаетесь к связанным данным. Например, выводите список заказов и для каждого заказа получаете имя покупателя. Если заказов 100, то будет 101 запрос: один на заказы и 100 на покупателей.

ActiveQuery предлагает решение — жадная загрузка через with(). Вы заранее говорите: "загрузи сразу всех покупателей для этих заказов". Yii2 сгенерирует два запроса: один для заказов, второй для покупателей (с условием IN). Вот как это выглядит:

$orders = Order::find()
    ->with('customer')
    ->all();

foreach ($orders as $order) {
    echo $order->customer->name; // не вызовет дополнительный запрос
}

Теперь всего два запроса. Магия? Нет, просто правильное использование инструмента.

Но не переусердствуйте: если вы подгружаете слишком много связей, запрос может стать тяжелым. Включайте в with() только то, что действительно нужно на странице.

Индексы: ускоряем поиск без изменения кода

Иногда проблема не в количестве запросов, а в их скорости. Если у вас таблица с миллионом записей и вы ищете по полю, на котором нет индекса, база данных будет сканировать всю таблицу. Это как искать иголку в стоге сена, перебирая каждую соломинку.

Индексы — это указатели, которые позволяют базе данных быстро находить нужные строки. В Yii2 вы можете создавать миграции для добавления индексов. Например, если вы часто фильтруете заказы по статусу:

$this->createIndex(
    'idx-order-status',
    '{{%order}}',
    'status'
);

Но не стоит индексировать все подряд. Индексы замедляют вставку и обновление записей, потому что их нужно обновлять. Индексируйте только те поля, по которым часто фильтруете или сортируете.

Правило: Индекс на поле имеет смысл, если селективность поля высокая (много разных значений) и запросы с фильтром по нему выполняются часто. Не индексируйте булевы поля (типа флага active), они мало помогают.

Кеширование: не долбим базу по пустякам

Если одни и те же данные запрашиваются снова и снова, зачем каждый раз лезть в базу? Yii2 предоставляет удобный механизм кеширования. Вы можете кешировать результаты запроса на определенное время.

$users = User::find()
    ->where(['active' => 1])
    ->cache(60) // кешировать на 60 секунд
    ->all();

Этот код сохранит результат в кеше (например, в Redis или Memcached) на минуту. Если запрос повторится в течение этого времени, Yii2 возьмет данные из кеша, не обращаясь к базе. Это особенно полезно для редко меняющихся данных: списки стран, настройки, категории.

Но помните: кеш — это компромисс. Если данные обновляются часто, вы рискуете показывать устаревшую информацию. Поэтому выбирайте время кеширования с умом.

Собираем все вместе: пример из жизни

Представьте, что у вас есть страница со списком заказов и их покупателями. Вот как может выглядеть оптимизированный запрос:

$orders = Order::find()
    ->select(['id', 'total', 'customer_id', 'status'])
    ->with(['customer' => function ($query) {
        $query->select(['id', 'name']); // только нужные поля покупателя
    }])
    ->where(['status' => 'completed'])
    ->orderBy(['created_at' => SORT_DESC])
    ->limit(20)
    ->asArray()
    ->cache(30) // кешируем на 30 секунд
    ->all();

Этот запрос:

  • выбирает только нужные поля заказа и покупателя;
  • использует жадную загрузку, чтобы избежать N+1;
  • ограничивает результат 20 записями;
  • кеширует результат на 30 секунд.

И все это — один вызов метода. Красиво, правда?

Что в итоге

Оптимизация запросов в Yii2 — это не магия, а набор простых привычек. Используйте select() для выборки только нужных полей, with() для жадной загрузки, создавайте индексы для частых фильтров и кешируйте результаты, когда это уместно. Эти простые шаги могут ускорить ваше приложение в разы, не требуя апгрейда сервера.

Помните: база данных — это не бездонный колодец, а ресурс, которым нужно управлять. ActiveQuery дает вам все инструменты для этого. Осталось только правильно ими воспользоваться.

Студия WNDER