Если ваш сайт на Yii2 начинает тормозить при росте данных, скорее всего, проблема в запросах. ActiveQuery — это мощный инструмент, который может превратить медленные выборки в молниеносные. В этой статье разберём, как правильно готовить запросы, чтобы база данных не становилась узким горлышком.

Digital-студия WNDER

Почему ActiveQuery, а не чистый SQL?

Многие думают: «Зачем эти абстракции, если можно написать SQL руками?» Но ActiveQuery даёт удобство и безопасность. Вы работаете с объектами, а не с строками, и при этом можете строить сложные запросы. Главное — понимать, как он работает под капотом.

ActiveQuery — это класс, который представляет собой запрос к базе. Он умеет ленивую загрузку, жадную загрузку, кэширование и многое другое. Но если использовать его неаккуратно, он сгенерирует чудовищный SQL, который положит сервер.

Правило: Всегда проверяйте, какой SQL генерирует ActiveQuery. Используйте ->createCommand()->rawSql или отладчик Yii.

Жадная загрузка против ленивой: кто кого?

По умолчанию Yii2 использует ленивую загрузку (lazy loading). Это значит, что связанные данные подгружаются только при обращении к ним. Удобно, но может привести к проблеме N+1.

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

Решение — жадная загрузка (eager loading) через with().

// Плохо: 101 запрос
$articles = Article::find()->all();
foreach ($articles as $article) {
    echo $article->author->name;
}

// Хорошо: 2 запроса
$articles = Article::find()->with('author')->all();
foreach ($articles as $article) {
    echo $article->author->name;
}

Жадная загрузка сначала выбирает все статьи, а потом одним запросом подтягивает всех авторов. Разница ощутима.

Выбирайте только нужные поля

Когда вы делаете Article::find()->all(), Yii выбирает все поля из таблицы. Если в таблице 50 колонок, а вам нужны только 3, вы тратите память и время на передачу лишних данных.

Используйте select(), чтобы указать конкретные поля.

// Выбираем только id и title
$articles = Article::find()
    ->select(['id', 'title'])
    ->all();

Но будьте осторожны: если вы потом обратитесь к полю, которого нет в select, Yii вернёт null. Это может сломать логику.

Лайфхак: Используйте asArray(), если вам не нужны объекты ActiveRecord. Это экономит память и ускоряет выборку.

Индексы и условия WHERE

ActiveQuery позволяет строить сложные условия, но не забывайте про индексы. Если вы фильтруете по полю, на котором нет индекса, база будет сканировать всю таблицу.

Пример: Article::find()->where(['status' => 1])->all(). Если по status нет индекса, при миллионе записей это будет медленно.

Добавьте индекс в миграции:

$this->createIndex('idx-article-status', 'article', 'status');

Также избегайте функций в WHERE, например WHERE YEAR(created_at) = 2023. Это убивает индекс. Лучше использовать диапазон:

$articles = Article::find()
    ->where(['>=', 'created_at', '2023-01-01'])
    ->andWhere(['<', 'created_at', '2024-01-01'])
    ->all();

Кэширование запросов

Если данные меняются редко, а запрос тяжёлый, используйте кэширование. ActiveQuery умеет кэшировать результаты.

$articles = Article::find()
    ->where(['status' => 1])
    ->cache(3600) // кэш на час
    ->all();

Но помните: кэш нужно сбрасывать при изменении данных. Можно использовать зависимости кэша.

$dependency = new \yii\caching\DbDependency([
    'sql' => 'SELECT MAX(updated_at) FROM article'
]);

$articles = Article::find()
    ->where(['status' => 1])
    ->cache(3600, $dependency)
    ->all();

Теперь кэш автоматически сбросится, если любая статья обновится.

Пакетная обработка больших данных

Если вам нужно обработать много записей, не загружайте их все в память. Используйте batch() или each().

// Обрабатываем по 100 записей
foreach (Article::find()->batch(100) as $articles) {
    foreach ($articles as $article) {
        // что-то делаем
    }
}

Это снижает потребление памяти и предотвращает падение при больших выборках.

Сравнение методов оптимизации

Метод Когда использовать Эффект
with() Связанные данные в цикле Убирает N+1
select() Нужны не все поля Меньше данных
asArray() Не нужны объекты Экономия памяти
cache() Редко меняющиеся данные Снижение нагрузки
batch() Обработка больших выборок Контроль памяти

Что в итоге?

ActiveQuery — это не просто удобная обёртка, а инструмент, который при правильном использовании даёт серьёзный прирост производительности. Главные правила: используйте жадную загрузку, выбирайте только нужные поля, не забывайте про индексы, кэшируйте тяжёлые запросы и обрабатывайте данные пакетами.

Начните с малого: проверьте свои запросы на наличие N+1 и добавьте with(). Уверен, вы удивитесь, насколько быстрее станет сайт.

Студия WNDER