PHP: Bulk Fetching - избавляемся от N+1 запросов

Bulk fetching (или «загрузка пакетами») — универсальный приём оптимизации баз данных. Вместо того чтобы выполнять один запрос на каждую запись (N+1 запросов), мы получаем список ID, а затем загружаем связанные данные одним-двумя пакетными запросами.

Проблема N+1

// Плохой подход
$users = $db->query("SELECT id, name FROM users");
foreach ($users as $u) {
    $u['posts'] = $db->query("SELECT * FROM posts WHERE user_id = {$u['id']}");
}

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

Решение: Bulk Fetching

// Хороший подход: 3 запроса максимум
$users = $db->query("SELECT id, name FROM users")->fetchAll(PDO::FETCH_ASSOC);

// 1. Собираем все ID
$userIds = array_column($users, 'id');

// 2. Загружаем все посты одним запросом
$posts = $db->query("
    SELECT * FROM posts WHERE user_id IN (" . implode(',', array_map('intval', $userIds)) . ")
")->fetchAll(PDO::FETCH_ASSOC);

// 3. Группируем посты по пользователям
$postsByUser = [];
foreach ($posts as $p) {
    $postsByUser[$p['user_id']][] = $p;
}

// 4. Склеиваем данные в PHP
foreach ($users as &$u) {
    $u['posts'] = $postsByUser[$u['id']] ?? [];
}
unset($u);

Теперь мы сделали всего 2 SQL-запроса — один на пользователей и один на посты. Производительность не зависит от количества записей, и код остаётся простым.

Универсальный шаблон

function bulkAttach(array $base, callable $fetchRelated, string $key = 'id', string $foreignKey = 'parent_id'): array {
    if (!$base) return [];
    $ids = array_map(fn($x) => (int)$x[$key], $base);
    $related = $fetchRelated($ids);

    $map = [];
    foreach ($related as $row) {
        $map[$row[$foreignKey]][] = $row;
    }

    foreach ($base as &$item) {
        $item['related'] = $map[$item[$key]] ?? [];
    }
    unset($item);
    return $base;
}

// Пример использования:
$users = getUsers(); // любые данные
$users = bulkAttach($users, fn($ids) => getPostsByUsers($ids));

Приём bulk fetching помогает избежать типичной проблемы оптимизация запросов к базе данных в цикле - той самой ситуации, когда каждый элемент делает свой SQL-запрос. Используя пакетную загрузку данных (batch loading), вы избавляетесь от N+1 запросов, ускоряете выборку связанных таблиц и повышаете производительность PHP-приложения без перехода на сложные ORM-решения. Если вы ищете, как ускорить работу каталога товаров, объединить несколько SQL-запросов в один или просто понять, что значит N+1-проблема в базе данных — этот подход даёт понятный и практичный ответ.

Просмотров: 298
Материал помог? Оценка помогает находить устаревшие и спорные решения.

Комментарии

0

Нашли ошибку или хотите дополнить материал?

Комментариев пока нет.Можно первым дополнить материал или сообщить об ошибке.
Telegram