К основному содержимому

EXPLAIN и EXPLAIN ANALYZE: как читать план запроса

MySQL / MariaDB · 29.09.2026

Что показывает 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 близка к фактической.
← Назад в базу знаний Задать вопрос поддержке