Производительность 1С-Битрикс - почему тормозит и что делать
Тормозит обычно не база, а запросы, которые к ней делает код. Разберём уровни оптимизации по порядку - от кода к инфраструктуре - и сквозные правила, которые не дают создать узкое место на ровном месте.
Как это работает
Три причины проблем на больших объёмах: конфликт одновременных запросов к базе, очередь даже из быстрых запросов и тяжёлые запросы, которые выполняются часто. Все три упираются в качество кода.
Оптимизировать нужно снизу вверх, по слоям.
- Код и запросы. Первый и главный уровень: не делать запросов в цикле, не тянуть лишние поля, фильтровать в запросе, а не в PHP. Об этом статья.
- Автокеширование компонентов. Штатные компоненты кешируют результат и на повторных запросах не идут в базу вовсе.
- Управляемый и тегированный кеш, собственный кеш данных. Точечный сброс по ключу или тегу при изменении данных.
- Композитный сайт. Готовый HTML отдаётся мгновенно, динамика догружается отдельно.
- Инфраструктура. Акселератор кода, настройка веб-сервера и базы, а при исчерпании одного сервера - веб-кластер.
Диагностика на всех уровнях - модуль «Монитор производительности»: он измеряет время и число запросов по компонентам, находит медленные запросы и подсказывает индексы.
Важное правило постановки задачи: цель должна быть измеримой. Не «сделать быстрее», а «главная отдаётся за столько-то миллисекунд при такой-то нагрузке». Иначе оптимизация не имеет конца.
Правила и примеры
1. Никогда не делайте запросов в цикле
Это причина номер один в проектах, которые «внезапно» легли под нагрузкой.
// ПЛОХО: один запрос на каждый элементforeach ($items as $item) { $author = CIBlockElement::GetByID($item['PROPERTY_AUTHOR_VALUE'])->Fetch(); $item['AUTHOR_NAME'] = $author['NAME'];}// ХОРОШО: собрать идентификаторы, сделать один запрос, связать по ключу$authorIds = array_unique(array_column($items, 'PROPERTY_AUTHOR_VALUE'));
$authors = [];$rs = CIBlockElement::GetList([], ['IBLOCK_ID' => 8, 'ID' => $authorIds], false, false, ['ID', 'NAME']);while ($row = $rs->Fetch()) { $authors[$row['ID']] = $row['NAME'];}
foreach ($items as &$item) { $item['AUTHOR_NAME'] = $authors[$item['PROPERTY_AUTHOR_VALUE']] ?? '';}Критерий простой: число запросов не должно расти вместе с числом элементов.
Ещё лучше - вообще не делать второй запрос, а получить связанные данные сразу:
CIBlockElement::GetList([], $filter, false, false, ['ID', 'NAME', 'PROPERTY_AUTHOR.NAME', 'PROPERTY_AUTHOR.PREVIEW_PICTURE']);2. Выбирайте только нужные поля
// ПЛОХО: тянет все поля и свойства элемента$element = CIBlockElement::GetByID($id)->Fetch();
// ХОРОШО: явный список полей$element = CIBlockElement::GetList([], ['ID' => $id], false, false, ['ID', 'NAME', 'DETAIL_PAGE_URL'])->Fetch();Точная выборка снижает расход памяти базы при сортировке и заметно ускоряет запрос на больших таблицах.
3. Фильтруйте в запросе, а не в PHP
// ПЛОХО: выбрали всех и отфильтровали в цикле// ХОРОШО: условие в запросе$rs = CUser::GetList('ID', 'ASC', ['GROUPS_ID' => [5]]);Сюда же относится фильтрация инфоблока по идентификатору, а не по символьному коду: код требует дополнительного запроса и джойна.
4. Не превращайте сравнение в поиск подстроки
// это LIKE '%xxx%'['CODE' => 'xxx']
// это точное сравнение['=CODE' => 'xxx']В ORM метод where() генерирует точное сравнение сам. В легаси-фильтре оператор
нужно писать явно - иначе платформа сделает поиск по подстроке, который не может
использовать индекс целиком.
5. Не считайте COUNT, если он не нужен
// два запроса: COUNT и SELECT['nPageSize' => 20]
// один запрос с LIMIT['nTopCount' => 20]Подсчёт общего количества нужен только для постраничной навигации. Для блока «последние новости» он лишний.
6. Специализированные методы вместо универсальных
Смена статуса оплаты заказа делается специальным методом, а не общим обновлением полей: он гарантирует вызов всех обработчиков оплаты. Универсальный метод формально сработает, но бизнес-логика вокруг него не выполнится.
7. Тяжёлое - из хита
Пересчёт счётчиков, агрегаты, обмен с внешними системами не должны выполняться при каждом показе страницы. Их место - агент по расписанию, фоновая задача после отдачи ответа или очередь сообщений.
Справочник
| Приём | Что даёт | Где подробнее |
|---|---|---|
| Один запрос вместо цикла | линейный рост запросов превращается в константу | эта статья |
| Явный список полей | меньше данных и памяти при сортировке | эта статья |
| Связанные данные в одном запросе | устраняет проблему N+1 | Инфоблоки |
| Точный оператор фильтра | запрос использует индекс | D7 ORM |
nTopCount вместо nPageSize | один запрос вместо двух | эта статья |
| Автокеш компонента | повторные показы без обращения к базе | Кеширование |
| Тегированный кеш | точечный сброс при изменении данных | Кеширование |
| Композитный сайт | мгновенная отдача готового HTML | Композитный сайт |
| Монитор производительности | измерение и поиск узких мест | Монитор |
| Веб-кластер | масштабирование за пределы одного сервера | Кластер |
Частые ошибки
Оптимизируют инфраструктуру вместо кода. Более мощный сервер лечит симптом: запрос в цикле останется запросом в цикле, просто порог падения сдвинется.
Кеш вместо исправления запроса. Кеш скрывает проблему до первого сброса. При инвалидации весь трафик разом уходит в базу с теми же тяжёлыми запросами.
Выборка «всего на всякий случай». Универсальный метод получения элемента тянет все поля и свойства, включая те, что не нужны на странице.
Подсчёт общего количества там, где он не нужен. Постраничная навигация стоит дополнительного запроса на каждой странице.
Фильтрация в PHP после выборки. База умеет фильтровать эффективнее, к тому же не придётся тащить лишние строки по сети и держать их в памяти.
Оптимизация без измерений. Без монитора производительности и профилирования легко потратить неделю на участок, который занимал два процента времени.
Частые вопросы
С чего начинать оптимизацию тормозящего сайта?
С измерения. Включите монитор производительности и посмотрите, где реально уходит время: число запросов на страницу, самые медленные запросы, распределение по компонентам. Дальше по порядку: убрать запросы в цикле, сократить выборки, добавить недостающие индексы, и только потом - кеширование и инфраструктура. Обратный порядок обычно заканчивается покупкой сервера, который лечит симптомы.
Разве кеш не решает проблему медленных запросов?
Только пока он валиден. В момент сброса - после изменения данных, публикации новости, обновления цен - весь трафик приходит в базу одновременно и с теми же тяжёлыми запросами. Кеш надо ставить поверх нормального кода, а не вместо него.
Как понять, что в коде проблема N+1?
Признак в мониторе: число запросов на странице растёт вместе с количеством выводимых элементов - двадцать товаров дают двадцать с лишним запросов. В коде это выглядит как обращение к API внутри цикла: получение свойств, автора, картинки или цены для каждого элемента отдельно. Лечится сбором идентификаторов и одним запросом либо получением связанных полей сразу в основной выборке.
Что быстрее - ORM или старое API?
Сама по себе разница между ними мало что решает: медленным запрос делает не выбор API, а лишние поля, отсутствие индексов, джойны и запросы в цикле. ORM удобнее тем, что явно выражает фильтры и связи и не даёт случайно получить поиск по подстроке вместо сравнения. Для нового кода это аргумент в её пользу, но переписывание рабочего легаси ради скорости обычно не окупается.
Связанные темы
- Кеширование - все виды кеша платформы
- Монитор производительности - измерение и индексы
- Композитный сайт - мгновенная отдача HTML
- Веб-кластер - масштабирование
- База данных - бэкенды и шардинг
- Сайт тормозит: разбор причин - с чего начинать разбор
- Медленный сайт - решения по скорости
- Раздел Производительность