Что показывает EXPLAIN в MySQL
Команда EXPLAIN заставляет оптимизатор MySQL показать план выполнения запроса вместо того, чтобы его выполнять: какие таблицы читаются, в каком порядке, через какой индекс и сколько строк сервер планирует просмотреть. Без плана оптимизация запроса превращается в угадывание — EXPLAIN превращает её в проверку конкретных цифр.
План строится для SELECT, а также для UPDATE и DELETE — это удобно перед изменением большой таблицы, чтобы заранее увидеть, не уйдёт ли запрос в полное сканирование.
Ключевые столбцы плана: type, key, rows, Extra
Из десятка столбцов EXPLAIN на практике важны четыре:
| Столбец | Что означает |
|---|---|
| type | Способ доступа к таблице: const, eq_ref, ref, range, index, ALL |
| key | Индекс, который реально выбрал оптимизатор |
| rows | Оценка числа строк для просмотра на этом шаге |
| Extra | Пометки вроде Using filesort или Using temporary |
| key_len | Сколько байт индекса реально участвует в поиске |
Значение ALL в столбце type означает полное сканирование таблицы без индекса. Для таблицы в пару тысяч строк это не страшно, а для таблицы на 10 миллионов строк — прямой путь к медленному запросу. Подробно о том, как индексы влияют на type и key, написано в статье про оптимизацию индексов MySQL.
Чем EXPLAIN ANALYZE отличается от обычного EXPLAIN
Обычный EXPLAIN — это только предсказание оптимизатора, запрос не выполняется. EXPLAIN ANALYZE, доступный с MySQL 8.0.18, реально выполняет запрос и добавляет к плану фактическое время каждого шага и реальное число строк. Это позволяет сравнить прогноз с действительностью и найти шаг, где оценка оптимизатора разошлась с реальностью в разы.
Минус EXPLAIN ANALYZE — он выполняет запрос целиком, включая тяжёлые UPDATE. На проде такие проверки запускают на копии базы или в транзакции с последующим ROLLBACK.
Как читать план на примере запроса с JOIN
Возьмём типичный запрос заказов с join к клиентам и посмотрим на вывод построчно.
EXPLAIN SELECT o.id, c.email
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'paid'
ORDER BY o.created_at DESC
LIMIT 20;Если в строке для таблицы orders столбец type равен ref, а key указывает на индекс по status, оптимизатор нашёл нужные строки быстро. Если же type показывает ALL, а в Extra видно Using where; Using filesort, запрос сканирует всю таблицу и досортировывает результат в памяти или на диске — верный признак, что не хватает составного индекса по (status, created_at).
Частые проблемы, которые видно в плане
Несколько пометок в Extra почти всегда указывают на узкое место:
Using filesort— сервер сортирует строки отдельно, индекс под ORDER BY не подошёл.Using temporary— для GROUP BY или DISTINCT создаётся временная таблица, часто на диске.Using join buffer— для JOIN не нашлось индекса, и MySQL перебирает строки в памяти.- Большое значение rows на верхнем уровне плана при маленьком LIMIT в запросе.
Как запустить EXPLAIN и EXPLAIN ANALYZE
Синтаксис одинаково прост для обеих команд:
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE customer_id = 42;
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42;Формат JSON показывает больше деталей, включая стоимость каждого шага в условных единицах оптимизатора, и удобен для автоматических проверок в CI. Обычный табличный формат быстрее читается глазами при разовой диагностике. Найденные по плану медленные запросы удобно собирать через slow query log и разбирать пачкой, а не по одному.
Чек-лист оптимизации запроса по плану
Перед тем как закрыть тикет на медленный запрос, пройдитесь по пунктам:
- type для каждой таблицы в плане не хуже range, кроме заведомо маленьких справочников.
- key показывает реальный индекс, а не пусто.
- В Extra нет Using filesort и Using temporary на горячем пути.
- rows на верхнем уровне соразмерно ожидаемому результату, а не всей таблице.
- EXPLAIN ANALYZE подтвердил, что оценка rows близка к фактической.