Медленный запрос к базе - поиск, план, индекс
Находим тот единственный запрос, из-за которого страница отвечает секунду, и разбираемся, чем ему помочь.
Решение
Собираем запросы страницы:
$connection = \Bitrix\Main\Application::getInstance()->getConnection();$connection->startTracker();// ... код страницы или скриптаforeach ($connection->getTracker()->getQueries() as $query) { if ($query->getTime() > 0.05) { // интересны только долгие printf("%.3f c %s\n", $query->getTime(), $query->getSql()); }}Трассировщик показывает не только время работы, но и сам текст запроса. Порог отсечения важнее списка целиком: тысяча запросов по миллисекунде мешает увидеть один запрос на полсекунды.
Читаем план выполнения:
EXPLAIN SELECT ID, NAME FROM b_iblock_elementWHERE IBLOCK_ID = 12 AND ACTIVE = 'Y' ORDER BY SORT LIMIT 20;
-- type: ALL - таблица читается целиком, указатель не найден-- rows: 480000 - столько строк база рассчитывает просмотреть-- key: NULL - ни один индекс не подошёлПлан отвечает на главный вопрос: читается таблица целиком или по указателю к нужным строкам. Полный проход по большой таблице и есть та секунда, которую видит посетитель.
Подбираем индекс под реальный фильтр:
ALTER TABLE b_iblock_element ADD INDEX ix_active_sort (IBLOCK_ID, ACTIVE, SORT);-- порядок полей повторяет порядок в фильтре: сначала равенство, потом сортировка-- на большой таблице команда идёт минуты и блокирует записьПорядок полей в индексе повторяет порядок условий в самом запросе. Индекс на одно поле из трёх помогает частично, а составной закрывает и отбор, и сортировку за один проход.
Считаем цену индекса:
SHOW INDEX FROM b_iblock_element; -- что уже есть, чтобы не завести дубль-- каждый индекс обновляется при каждой вставке и правке строки-- на таблице обмена это превращается в заметное замедление выгрузкиИндекс ускоряет чтение строк и ровно так же замедляет их запись. На таблице, куда обмен пишет десятки тысяч строк, лишний указатель стоит дороже, чем экономит, и это проверяют замером до и после.
Замер повторяют после каждой отдельной правки, а не в самом конце работы. Иначе три изменения дают один результат, и никто не знает, какое из них сработало.
Правки индексов стоит хранить вместе с кодом решения. Указатель, добавленный руками на боевом сайте, не переживёт переноса и вернёт ту же секунду через месяц.
Штатным таблицам самой платформы индексы добавляют с осторожностью. Обновление продукта меняет их структуру, и о своём указателе оно ничего не знает.
Один медленный запрос стоит починить до того, как браться за настройки базы. Тюнинг сервера редко даёт тот же выигрыш, что убранный полный проход по таблице на четыреста тысяч строк.
Типичные проблемы
Индекс добавлен, а запрос быстрее не стал.
Фильтр идёт по выражению над полем, и указатель к нему не применяется. Условие переписывают на сравнение самого поля.
После добавления индексов замедлился обмен.
Каждая вставка строки обновляет все указатели таблицы. На таблицах активной записи их держат по минимуму.
На копии план хороший, на боевом плохой.
План зависит от объёма и распределения данных. Проверяют на копии с реальным объёмом, а не на пустой.
Запрос быстрый в консоли и медленный на сайте.
На сайте у него другие параметры и конкуренция за базу. Сравнивать нужно тот же запрос под нагрузкой.
Долгая команда изменения таблицы подвесила сайт.
Она блокирует запись на всё время работы. Такие правки делают в окно обслуживания или отдельным механизмом.
Частые вопросы
Как найти медленные запросы без правки кода?
Журналом медленных запросов самой базы. Он пишет всё, что дольше заданного порога, без участия сайта.
Почему выборка с фильтром по свойству такая тяжёлая?
Значения свойств лежат в отдельных таблицах, и фильтр требует соединения. Для каталога это решает фасетный индекс.
Стоит ли выбирать все поля сразу?
Нет: лишние поля тянут данные и мешают базе обойтись одним указателем. В выборке перечисляют только нужное.
Когда пора менять структуру, а не индексы?
Когда запрос уже идёт по указателю, а строк всё равно миллионы. Тогда помогает отдельная таблица с готовыми данными.
Смежное
- Запросы к базе - оглавление подтемы
- Трекер SQL-запросов: счётчик запросов участка кода - как найти виновный участок кода
- Монитор производительности: замер, отчёты, поиск узких мест - чем находят такие запросы
- Сайт тормозит: с чего начать поиск причины - шаг раньше по времени
- База растёт: журналы, статистика, старые данные и чистка - когда дело в объёме служебных таблиц
- Монитор производительности - штатный сбор замеров
- Кэширование своей выборки: ключ, теги, сброс - когда запрос лучше не повторять
- Производительность - устройство темы целиком
- Связи между таблицами ORM: ссылки, выборка, удаление - соединение без указателя
- Фасетный индекс: зачем нужен и когда пересобирать - фильтр каталога лечится им
- Слишком много соединений с базой: разбор причин - когда долгие запросы копят соединения
- Настройка базы под нагрузку: буферы, движок, соединения - когда дело не в запросе, а в настройках сервера