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-проблема в базе данных — этот подход даёт понятный и практичный ответ.
Комментарии
0Нашли ошибку или хотите дополнить материал?