Десять приёмов MySQL для производительности проекта на Битрикс
Зачем это в контексте Битрикс
Кастомные модули, агенты и тяжёлые выборки из b_iblock_element напрямую идут в MySQL (InnoDB). Ниже — практики уровня приложения и схемы, без «магии» ядра: они снижают нагрузку на диск и CPU так же, как аккуратная настройка компонентов на PHP.
1. LIMIT 1, если нужен только факт наличия
Для вопроса «есть ли хоть одна запись» разумно не тащить все колонки. Пустой результат отсутствия строки вы получите и от SELECT 1 … LIMIT 1, при этом серверу проще остановиться на первой найденной строке индекса.
2. Стабильный текст SQL и кеширование
В классических примерах «плохо» встроены функции вроде CURDATE() в самом тексте — такой запрос каждый календарный день выглядит для кеша как новый. Лучше подставлять границу даты из PHP (или константы агента) так, чтобы строка запроса была предсказуемой.
У старого Query Cache в MySQL 5.x это имело прямой смысл; в MySQL 8.0 кеш запросов на стороне СУБД сняли, но принцип остался: стабильный SQL упрощает сравнение планов и ключи для прикладного кеша (в т. ч. CPHPCache и своих обёрток).
3. Индексы под реальные фильтры
Условие вида last_name LIKE 'А%' может использовать индекс по префиксу. Шаблон LIKE '%текст%' по полю без полнотекстового поиска почти всегда вынуждает полный просмотр. На таблицах в несколько сотен строк выигрыш от индекса может быть близок к нулю; на миллионах строк в каталоге — критичен.
4. JOIN и одинаковые типы
Столбцы, по которым вы соединяете заказы и справочники, должны совпадать по типу и по кодировке/сравнению строк. Смешение INT и DECIMAL или разных CHARSET ломает использование индекса — оптимизатор вынужден приводить типы и перебирать больше строк, чем нужно для выборок в отчётах или кастомных админках.
5. Не раздувайте SELECT без необходимости
SELECT * тянет все BLOB/TEXT и лишние поля; на узком канале между приложением и БД это заметно. В шаблоне достаточно перечислить реально используемые колонки — тем более в собственных SQL внутри модулей, где нет ORM-магии.
6. Избегайте ORDER BY RAND()
Случайная «одна карточка» через ORDER BY RAND() заставляет сервер упорядочивать огромный набор. Типовой обход — посчитать количество подходящих строк, выбрать случайное смещение в PHP и применить LIMIT offset, 1 (с оговорками по равномерности и пустым таблицам; для витрин часто делают отдельную предвыборку ID).
7. Перечисления, строки и смена схемы
Сжатый набор фиксированных значений иногда хранят как ENUM или как короткое целое с расшифровкой в коде. У ENUM плюс — компактность; минус — дорогой ALTER на больших таблицах и связанность приложения с порядком значений в определении. Для часто меняющихся статусов удобнее отдельный справочник и внешний ключ.
8. IP-адрес как целое
Для логов и антифрода UNSIGNED INT плюс INET_ATON/INET_NTOA (на PHP — ip2long/long2ip с учётом знаковости) экономит место относительно VARCHAR(45); для IPv6 смотрите отдельно — там уже VARBINARY(16) или нативные типы в новых версиях.
9. «Разрезать» таблицу по смыслу
Редко читаемые текстовые поля или часто обновляемый «счётчик последнего входа» можно вынести в соседнюю таблицу с 1:1, чтобы основная оставалась компактнее для типовых списков. Цена — лишний JOIN; решение оправдано при измеримой экономии I/O, а не «на всякий случай».
10. Движок хранения
InnoDB даёт строковые блокировки и транзакции; MyISAM в типовом Bitrix-проекте давно не рекомендуется для рабочих сущностей. Для крупных каталогов и заказов выбирайте InnoDB и нормальную изоляцию; специализированные витрины «только чтение» планируйте отдельно, если архитектура это позволяет.
Итог
Эти приёмы не заменяют профилирование конкретного узкого запроса на копии базы, но формируют единый стиль: меньше лишних данных, предсказуемее план и меньше сюрпризов под нагрузкой реального магазина или портала.
-- проверка «есть ли строка» вместо SELECT *
SELECT 1 FROM demo_region WHERE code = 'CA' LIMIT 1;
-- префикс может использовать индекс; '%x%' — почти никогда
SELECT id FROM demo_client WHERE last_name LIKE 'И%';
SELECT id FROM demo_article WHERE body_plain LIKE '%слово%';<?php
// Стабильная граница даты — в переменной, не через CURDATE() в SQL
$today = (new DateTimeImmutable('today'))->format('Y-m-d');
$sql = "SELECT login_name FROM demo_user WHERE signup_day >= '" . $today . "'";
// дальше — mysqli_query / PDO по стилю проекта
// Случайная одна строка без ORDER BY RAND(): грубый набросок
$cntRow = $db->query("SELECT COUNT(*) AS c FROM demo_user WHERE flags = 1")->fetch_assoc();
$pick = $cntRow['c'] > 0 ? random_int(0, (int) $cntRow['c'] - 1) : 0;
// SELECT ... LIMIT $pick, 1 — после проверки типов и экранирования
UPDATE demo_event SET src_ip = INET_ATON('203.0.113.44') WHERE event_id = 91502; Не хотите копаться сами?
Починю за 1-3 дня. Без предоплаты — оплата по результату.
15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии