Михаил Базаров Разработка на 1С-Битрикс Михаил Базаров

Как избегать N+n запросы при работе с getList методами в Битрикс. Дай несколько реалистичных примеров "плохих" и хороших, что бы было наглядно

Вопрос задан: 26 мая 2026 в 01:00 | Просмотров: 56 Использованные MCP инструменты: searchDocs, liveApiFindFunction, liveApiGetFunctionCode

Задайте свой вопрос — Работает AI в связке с MCP сервером. Перепроверяйте важную информацию.

Проблема N+1 запросов в 1С-Битрикс: причины и решения

Проблема N+1 возникает, когда в цикле для каждой выбранной записи выполняется дополнительный запрос к БД. Вместо 1 запроса получается 1 + N запросов. Чем больше N, тем сильнее деградация производительности.

Рассмотрим четыре реалистичных сценария — «плохой» и «хороший» для каждого.


Пример №1. Классический API: CIBlockElement — получение пользователя, создавшего элемент

❌ ПЛОХОЙ код (N+1):

// 1 запрос — получить 100 элементов
$rsItems = CIBlockElement::GetList(
    [],
    ['IBLOCK_ID' => 5, 'ACTIVE' => 'Y'],
    false,
    false,
    ['ID', 'NAME', 'CREATED_BY']
);

while ($arItem = $rsItems->Fetch()) {
    // +1 запрос в цикле на каждой итерации!
    $rsUser = CUser::GetByID($arItem['CREATED_BY']);
    $arUser = $rsUser->Fetch();
    echo $arItem['NAME'] . ' — автор: ' . $arUser['NAME'] . '<br>';
}
// Итого: 1 (элементы) + 100 (пользователи) = 101 SQL-запрос

✅ ХОРОШИЙ код (1 запрос с GROUP / пакетная загрузка):

// 1 запрос — получаем все ID пользователей
$rsItems = CIBlockElement::GetList(
    [],
    ['IBLOCK_ID' => 5, 'ACTIVE' => 'Y'],
    false,
    false,
    ['ID', 'NAME', 'CREATED_BY']
);

$userIds = [];
$items = [];
while ($arItem = $rsItems->Fetch()) {
    $items[] = $arItem;
    $userIds[] = (int)$arItem['CREATED_BY'];
}
$userIds = array_unique($userIds);

// 1 запрос — получаем ВСЕХ нужных пользователей разом
$users = [];
if (!empty($userIds)) {
    $rsUsers = CUser::GetList(
        'ID', 'ASC',
        ['ID' => implode('|', $userIds)]
    );
    while ($arUser = $rsUsers->Fetch()) {
        $users[$arUser['ID']] = $arUser;
    }
}

// Обработка — без единого запроса в цикле
foreach ($items as $arItem) {
    $authorName = $users[$arItem['CREATED_BY']]['NAME'] ?? 'Неизвестно';
    echo $arItem['NAME'] . ' — автор: ' . $authorName . '<br>';
}
// Итого: 2 SQL-запроса (элементы + пользователи)

Пример №2. ORM D7: связанные сущности через Reference

❌ ПЛОХОЙ код (N+1 через ручной fetch):

use Bitrix\Main\UserTable;

// 1 запрос
$rsUsers = UserTable::getList([
    'select' => ['ID', 'NAME', 'LAST_NAME'],
    'limit'  => 50,
]);

$users = [];
while ($user = $rsUsers->Fetch()) {
    // +1 запрос в цикле к каждой итерации!
    $rsGroups = \Bitrix\Main\UserGroupTable::getList([
        'filter' => ['=USER_ID' => $user['ID']],
        'select' => ['GROUP_ID'],
    ]);

    $groups = [];
    while ($g = $rsGroups->Fetch()) {
        $groups[] = $g['GROUP_ID'];
    }
    $user['GROUPS'] = $groups;
    $users[] = $user;
}
// Итого: 1 + 50 = 51 SQL-запрос

✅ ХОРОШИЙ код (жадная загрузка через select + Reference):

use Bitrix\Main\UserTable;
use Bitrix\Main\ORM\Query\QueryHelper;

// Строим 1 запрос с JOIN через Reference
$query = UserTable::query()
    ->setSelect([
        'ID',
        'NAME',
        'LAST_NAME',
        // Жадная загрузка связанной сущности (LEFT JOIN)
        'GROUPS_' => 'GROUPS.GROUP_ID',
    ])
    ->setLimit(50);

// Используем QueryHelper::decompose для корректной работы с лимитом и связями
$collection = QueryHelper::decompose($query, true, true);

foreach ($collection as $user) {
    $groups = [];
    foreach ($user->get('GROUPS_') as $group) {
        $groups[] = $group['GROUP_ID'];
    }
    echo $user['NAME'] . ' — группы: ' . implode(', ', $groups) . '<br>';
}
// Итого: 2 SQL-запроса (основная выборка + связи), независимо от N

Пример №3. Классический API: получение свойств элементов инфоблока

❌ ПЛОХОЙ код (N+1 через GetProperty):

$rsItems = CIBlockElement::GetList(
    [],
    ['IBLOCK_ID' => 10, 'ACTIVE' => 'Y'],
    false,
    ['nTopCount' => 30],
    ['ID', 'NAME']
);

while ($arItem = $rsItems->Fetch()) {
    // +1 запрос на каждый элемент!
    $dbProps = CIBlockElement::GetProperty(
        $iblockId, $arItem['ID']
    );
    echo $arItem['NAME'] . ': ';
    while ($arProp = $dbProps->Fetch()) {
        if ($arProp['CODE'] === 'ARTNUMBER') {
            echo $arProp['VALUE'];
        }
    }
    echo '<br>';
}
// Итого: 1 + 30 = 31 SQL-запрос

✅ ХОРОШИЙ код (1 запрос через GetList с PROPERTY_* в select):

$rsItems = CIBlockElement::GetList(
    [],
    ['IBLOCK_ID' => 10, 'ACTIVE' => 'Y'],
    false,
    ['nTopCount' => 30],
    [
        'ID',
        'NAME',
        'PROPERTY_ARTNUMBER',  // свойство подтягивается одним JOIN
    ]
);

while ($arItem = $rsItems->Fetch()) {
    echo $arItem['NAME'] . ': ' . $arItem['PROPERTY_ARTNUMBER_VALUE'] . '<br>';
}
// Итого: 1 SQL-запрос (все данные за один раз)

Пример №4. ORM: вложенные связи ManyToMany / OneToMany

❌ ПЛОХОЙ код (N+1 при обходе связанных записей):

use Bitrix\Iblock\ElementTable;

$elements = ElementTable::getList([
    'select' => ['ID', 'NAME', 'IBLOCK_ID'],
    'filter' => ['=IBLOCK_ID' => 3],
    'limit'  => 20,
])->fetchAll();

foreach ($elements as $element) {
    // +1 запрос к свойству на каждый элемент!
    $propValue = \CIBlockElement::GetProperty(
        3, $element['ID'], [], ['CODE' => 'COLOR']
    )->Fetch();
    echo $element['NAME'] . ' — цвет: ' . $propValue['VALUE'] . '<br>';
}
// Итого: 1 + 20 = 21 SQL-запрос

✅ ХОРОШИЙ код (QueryHelper::decompose + runtime reference для свойств):

use Bitrix\Iblock\ElementTable;
use Bitrix\Main\ORM\Query\QueryHelper;

$query = ElementTable::query()
    ->setSelect([
        'ID',
        'NAME',
        'IBLOCK_ID',
    ])
    ->setFilter(['=IBLOCK_ID' => 3])
    ->setLimit(20);

// QueryHelper разобьёт запрос на основной + связи, избегая N+1
$collection = QueryHelper::decompose($query, true, true);

foreach ($collection as $element) {
    echo $element['NAME'] . ' (ID: ' . $element['ID'] . ')<br>';
}
// Итого: 1-2 SQL-запроса, независимо от количества записей

Ключевые принципы избегания N+1

  1. Убирайте запросы из циклов. Сначала соберите все ID в массив, затем выполните один запрос с filter = ['ID' => $ids].
  2. Используйте PROPERTY_* и точечный select в классическом API. Указывайте только нужные поля и свойства — это даёт JOIN, а не N отдельных запросов.
  3. Используйте Reference / связи ORM. В D7 можно указать связанные поля в select, и ORM сам построит корректный JOIN.
  4. Применяйте QueryHelper::decompose(). Если у вас связи типа OneToMany / ManyToMany с LIMIT, этот метод разбивает запрос на основной + отдельные запросы для связей, но гарантированно без N+1 и без декартова произведения.
  5. Используйте кеш. Для редко меняющихся данных включите 'cache' => ['ttl' => 3600] в getList, чтобы не ходить в БД при каждом хите.

Услуги Стоимость разработки на 1С-Битрикс

Стоимость разработки сайта зависит от объёма и сложности проекта. Ниже приведены ориентировочные цены, как правило не выходят за обозначенные рамки. Срок разработки зависит от сложности проекта: как правило называю сроки с запасом.
Финальная стоимость и сроки разработки обговариваются на этапе обсуждения. Скачайте опросник на разработку, заполните как можно подробнее и вышлите удобным способом. После ознакомления смогу задать уточняющие вопросы и оценить проект.
Индивидуальная разработка магазина
от 250 000 руб. от 4-х недель

Разработка магазина на 1С-Битрикс с нуля. Дизайн, сборка и оптимизация производительности под конкретный проект и требования. Реализация любого функционала без ограничений готовых решений.

Подробнее
Сайт на готовом решении 1С-Битрикс
от 50 000 руб. от 5-ти дней

Вариант для тех, кто не хочет тратить много средств на индивидуальный проект, и не имеет серьезных требований к сайту. Магазин, быстро запускается на базе одного из 200-та готовых решений.

Подробнее
Мобильное приложение
от 300 000 руб. от 4-х недель

Разработка кроссплатформенного мобильного приложения, которое не уступает нативным решениям как в производительности, так и пользовательском опыте. Публикуется в AppStore, GooglePlay и RuStore

Подробнее
Сайт компании
от 120 000 руб. от 2-х недель

Корпоративный сайт с информационными разделами, каталогом товаров или услуг. Включает формы обратной связи карточек каталога, любое количество статичных и динамичных разделов.

Подробнее
Инфоресурс
от 100 000 руб. от 3-х недель

Информационный ресурс любой сложности. Сайт для СМИ, городской портал или многопользовательская доска объявлений. Внутренние форумы, блоги- по необходимости.

Подробнее
Поддержка и доработки проектов
от 3 000 руб. от 1 часа

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

Подробнее

Включено в стоимость разработки:

  • Лицензия на 1С-Битрикс необходимой редакции, дополнительные модули, для реализации функционала и видео-инструкции по работе с готовым проектом
  • Оптимизация программной части проекта и конфигурации сервера под максимальную скорость работы. Базовая СЕО оптимизация и добавление сайта в поисковые системы.

Блог-note Заметки по 1С-Битрикс