Перейти к содержимому

Трекер SQL-запросов - счётчик запросов участка кода и поиск лишних

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

Что нужно знать заранее

Трекер собирает запросы прямо в коде и не требует настройки сервера базы данных. Это его главное преимущество перед журналом медленных запросов, который показывает всё сразу и без привязки к участку.

У каждого собранного запроса есть текст, время выполнения и стек вызовов кода. Стек отвечает на самый важный вопрос разбора: какой именно код проекта отправил этот запрос к базе.

Опасность здесь одна: тексты запросов содержат значения фильтров и персональные данные. Журнал таких запросов кладут вне публичного каталога сайта, иначе его скачает первый же любопытный посетитель.

Шаги

  1. Обернуть подозрительный участок кода включением и выключением трекера запросов ядра.
  2. Прочитать собранные запросы вместе с их временем выполнения и полным стеком вызовов.
  3. Найти повторяющиеся тексты запросов: почти всегда это выборка внутри обычного цикла.
  4. Переписать участок так, чтобы данные забирались одной выборкой на всю страницу.
  5. Выключить трекер и убрать журнал запросов сразу после окончания всего разбора.

Решение

Включаем трекер вокруг участка кода:

$connection = \Bitrix\Main\Application::getConnection();
$connection->startTracker();
$items = renderCatalogSection($sectionId); // подозрительный участок кода
$tracker = $connection->getTracker();
$connection->stopTracker();
printf("запросов на участке: %d\n", count($tracker->getQueries()));

Счётчик запросов участка сразу показывает масштаб беды. Двадцать запросов на блок рекомендаций - это нормально, а двести на список товаров означают выборку внутри цикла по позициям.

Читаем текст, время и стек запроса:

foreach ($tracker->getQueries() as $query) {
printf("%.4f c | %s\n", $query->getTime(), mb_substr($query->getSql(), 0, 120));
printf("вызов: %s\n", $query->getTrace()[1]['function'] ?? '-'); // кто отправил запрос
}

Стек вызовов важнее текста запроса при разборе. Текст говорит, что именно спросили у базы, а стек - какой участок вашего кода это сделал, и правят обычно именно его.

Ищем повторяющийся запрос в цикле:

$byText = [];
foreach ($tracker->getQueries() as $query) {
$key = preg_replace('/\d+/', 'N', mb_substr($query->getSql(), 0, 80));
$byText[$key] = ($byText[$key] ?? 0) + 1; // одинаковый текст с разными числами
}
arsort($byText);
print_r(array_slice($byText, 0, 5, true)); // верхние строки - выборка в цикле

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

Сортируем запросы по времени выполнения:

$queries = $tracker->getQueries();
usort($queries, static fn($a, $b) => $b->getTime() <=> $a->getTime());
foreach (array_slice($queries, 0, 3) as $query) {
printf("%.4f c | %s\n", $query->getTime(), mb_substr($query->getSql(), 0, 100));
}

Пишем журнал запросов вне публичного каталога:

$tracker->startFileLog('/var/log/bitrix/sql_queries.log'); // вне корня сайта
// в текстах запросов лежат значения фильтров и персональные данные покупателей

Журнал в каталоге загрузок скачивается по прямой ссылке кем угодно. Его место - вне корня сайта, а после разбора файл удаляют вместе с включённым трекером.

Типичные проблемы

Страница делает сотни запросов к базе данных.

Выборка выполняется внутри цикла по элементам списка на странице. Данные забирают одной выборкой сразу по всему списку идентификаторов.

Трекер остался включённым на боевом сайте.

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

Журнал запросов оказался доступен снаружи.

Файл журнала лежит внутри публичного каталога сайта и скачивается по прямой ссылке. Журналы держат вне корня сайта, рядом с журналами самого сервера.

Счётчик запросов ничего не показал.

Участок работает из кэша и запросов к базе данных вообще не делает. Разбор ведут с выключенным кэшем компонента или сразу после его сброса.

В стеке вызовов видно только код ядра.

Смотрится только первый уровень стека вместо нескольких верхних уровней. Перебирают несколько уровней стека, пока не встретится код своего решения.

Частые вопросы

Чем трекер лучше журнала медленных запросов?

Он привязан к участку кода и показывает стек вызова каждого запроса. Журнал сервера видит все запросы сайта сразу и не знает, какой код их отправил.

Можно ли включать трекер на боевом сайте?

Кратковременно и под своей учётной записью - да, но лучше на копии. Постоянно включённый трекер расходует память и пишет лишнее.

Сколько запросов на страницу считается нормой?

Зависит от страницы, но десятки - обычно нормально, сотни - повод для разбора. Важнее не число, а повторяющиеся запросы с одинаковым текстом.

Как избавиться от выборки в цикле?

Собрать идентификаторы и сделать одну выборку по списку, а потом разложить результат. Это стандартный приём и он почти всегда применим.

Где смотреть запросы штатными средствами?

В панели отладки страницы и в мониторе производительности платформы. Трекер дополняет их точечным замером конкретного участка кода.

Смежное

Первоисточник