Профілювання MySQL-запитів 1С-Бітрікс

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Профілювання MySQL-запитів 1С-Бітрікс
Середня
~1-2 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Розробка на базі Бітрікс, Бітрікс24, 1С для компанії Development of an Online
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

Профілювання MySQL-запитів 1С-Бітрікс

Бітрікс активно працює з базою даних: модуль каталогу (b_catalog_price, b_iblock_element, b_iblock_element_prop_s*), пошук (b_search_content), сесії (b_user_session). При 20–50 тисячах товарів і ввімкнених властивостях інфоблоку типова сторінка каталогу генерує 50–200 SQL-запитів. Частина з них виконується за 1–2 мс, але кілька можуть займати по 500 мс — і саме вони визначають час відповіді сторінки.

Увімкнення slow query log

Перший інструмент — журнал повільних запитів MySQL. Вмикається без перезапуску сервера:

-- Динамічно, без перезавантаження MySQL
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 0.5;       -- запити довші за 500 мс
SET GLOBAL log_queries_not_using_indexes = 1;  -- запити без індексів

У /etc/mysql/conf.d/slow.cnf для постійного ефекту:

[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
log_queries_not_using_indexes = 1
min_examined_row_limit = 100

min_examined_row_limit = 100 прибирає шум від швидких запитів за первинним ключем, які «не використовують індекс» формально.

Аналіз через pt-query-digest

Percona Toolkit — стандарт аналізу slow log:

pt-query-digest /var/log/mysql/slow.log \
    --limit 20 \
    --report-format query_report \
    > /tmp/slow_report.txt

Звіт групує запити за нормалізованим шаблоном (замінює значення на ?), показує:

  • Calls — скільки разів виконувався
  • R/Call — середній час на виклик
  • R/t — сумарний час за період

На Бітрікс-проєктах топ-5 проблемних запитів зазвичай виглядає так:

  1. SELECT ... FROM b_iblock_element_prop_s5 WHERE IBLOCK_ELEMENT_ID IN (...) — N+1 при виборці властивостей
  2. SELECT ... FROM b_catalog_price WHERE PRODUCT_ID = ? — поштучна виборка цін
  3. SELECT COUNT(*) FROM b_iblock_element WHERE ... — COUNT без індексу по складеному фільтру
  4. SELECT ... FROM b_search_content WHERE ... — повнотекстовий пошук по великій таблиці
  5. UPDATE b_user_session SET ... — конкуренція за оновлення сесій

EXPLAIN та аналіз плану запиту

Для кожного повільного запиту зі звіту запускаємо EXPLAIN:

EXPLAIN SELECT be.ID, be.NAME, bp.VALUE
FROM b_iblock_element be
LEFT JOIN b_iblock_element_prop_s5 bp ON bp.IBLOCK_ELEMENT_ID = be.ID
WHERE be.IBLOCK_ID = 12
  AND be.ACTIVE = 'Y'
  AND be.WF_STATUS_ID = 1
ORDER BY be.SORT ASC
LIMIT 48 OFFSET 0;

На що дивитися у виводі EXPLAIN:

  • type = ALL (full table scan) — відсутній індекс, критично
  • type = ref або range — використовується індекс, нормально
  • rows — оцінка кількості рядків, що перевіряються; якщо на таблиці з 100k записів rows = 95000 — індекс не працює
  • Extra: Using filesort — сортування в пам'яті/на диску, може бути дорогим
  • Extra: Using temporary — створюється тимчасова таблиця
-- EXPLAIN ANALYZE (MySQL 8.0+) показує реальний час
EXPLAIN ANALYZE SELECT ...;

Створення індексів для типових запитів Бітрікс

Кілька індексів, які регулярно відсутні в стандартному встановленні або зникають після оновлень:

-- Складений індекс для фільтрації активних елементів за інфоблоком з сортуванням
CREATE INDEX idx_iblock_element_active_sort
ON b_iblock_element (IBLOCK_ID, ACTIVE, WF_STATUS_ID, SORT);

-- Індекс для виборки цін за продуктами та групами
CREATE INDEX idx_catalog_price_product_group
ON b_catalog_price (PRODUCT_ID, CATALOG_GROUP_ID);

-- Індекс для пошуку по сесіях (якщо b_user_session без кешу)
CREATE INDEX idx_user_session_timestamp
ON b_user_session (TIMESTAMP_X);

Після створення індексу перезапускаємо EXPLAIN — тип повинен змінитися з ALL на ref або range.

Бітрікс ORM та N+1

D7 ORM (\Bitrix\Main\ORM) часто генерує N+1 при роботі зі зв'язаними сутностями. Діагностика — через вбудований дебагер або перехоплення SQL:

// Логуємо всі запити в dev-оточенні
\Bitrix\Main\Application::getConnection()->setTracker(
    new \Bitrix\Main\DB\SqlTracker(50)
);

// Наприкінці запиту:
$tracker = \Bitrix\Main\Application::getConnection()->getTracker();
foreach ($tracker->getQueries() as $query) {
    if ($query->getTime() > 0.1) {
        error_log($query->getSql() . ' [' . $query->getTime() . 's]');
    }
}

Типовий N+1 в ORM-коді:

// ПОГАНО: запит до b_catalog_price на кожній ітерації
$elements = ElementTable::getList(['filter' => ['IBLOCK_ID' => 12]]);
while ($el = $elements->fetch()) {
    $price = PriceTable::getRow(['filter' => ['PRODUCT_ID' => $el['ID']]]); // N запитів
}

// ДОБРЕ: пакетна виборка
$ids = array_column($elements->fetchAll(), 'ID');
$prices = PriceTable::getList(['filter' => ['PRODUCT_ID' => $ids]])->fetchAll();
$priceMap = array_column($prices, null, 'PRODUCT_ID');

Кейс: оптовий дистриб'ютор

Сайт на Бітрікс «Малий бізнес», каталог 28 000 позицій, 3 000 унікальних відвідувачів на день. Сервер: 4 CPU, 8 GB RAM. Навантаження на MySQL — 85–90% CPU в пікові години, періодичні таймаути.

pt-query-digest за добу показав: 92% сумарного часу MySQL — запити до b_iblock_element_prop_s8 (таблиця рядкових властивостей). EXPLAIN показав type = ALL на 280 000 рядках — індекс по IBLOCK_ELEMENT_ID був відсутній.

-- Один індекс закрив проблему
CREATE INDEX idx_prop_s8_element_id ON b_iblock_element_prop_s8 (IBLOCK_ELEMENT_ID);

Навантаження на MySQL впало з 85–90% до 15–20% CPU без жодних змін у коді застосунку.

Інструменти моніторингу

  • Percona Monitoring and Management (PMM) — повнофункціональний стек, графіки QPS, latency, топ запитів у реальному часі
  • MySQL Workbench → Performance Schema — вбудований GUI, зручний для разової діагностики
  • Grafana + mysql_exporter — для інтеграції в наявний моніторинг

Терміни

Масштаб Склад Термін
Аудит Увімкнення slow log, аналіз, звіт 1–2 дні
Оптимізація Індекси, рефакторинг N+1 запитів, налаштування буферів MySQL 3–7 днів
Налаштування моніторингу PMM або Grafana + алерти 2–3 дні