Какие данные показывает команда EXPLAIN в SQL и как их интерпретировать?

«Какие данные показывает команда EXPLAIN в SQL и как их интерпретировать?» — вопрос из категории Базы данных, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

EXPLAIN (или EXPLAIN ANALYZE в PostgreSQL) — это основной инструмент для анализа плана выполнения запроса и поиска узких мест. Вот на что я смотрю в первую очередь:

Ключевые столбцы в выводе EXPLAIN (на примере MySQL):

  1. type (самый важный): Тип соединения. От него сильно зависит производительность.

    • system / const: Идеально. Запрос находит одну строку по первичному или уникальному ключу.
    • eq_ref: Отлично. Используется при JOIN по первичному ключу.
    • ref: Хорошо. Использование не-уникального индекса для поиска.
    • range: Нормально. Поиск по диапазону индекса (BETWEEN, IN, >).
    • index: Плохо. Полное сканирование индекса (индексный файл читается целиком).
    • ALL: Очень плохо. Полное сканирование таблицы (table scan). Требует добавления индекса.
  2. key: Какой индекс фактически используется. Если NULL — индекс не используется, что часто ведет к ALL.

  3. rows: Оценочное количество строк, которое движку БД придется проверить. Чем меньше, тем лучше. Резкое расхождение с реальным числом обработанных строк может указывать на устаревшую статистику.

  4. Extra: Дополнительная информация, критичная для оптимизации.

    • Using index: Отлично (Covering Index). Все нужные данные есть в самом индексе, чтение таблицы не требуется.
    • Using where: СУБД фильтрует строки после их чтения из таблицы или индекса.
    • Using temporary: Плохо. Создается временная таблица (часто при GROUP BY, ORDER BY без индекса).
    • Using filesort: Плохо. Требуется дополнительная сортировка во внешней памяти.

Практический пример анализа:

EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status = 'shipped';

Возможный вывод и интерпретация:

id select_type table type possible_keys key rows Extra
1 SIMPLE orders ref idx_user,idx_status idx_user 50 Using where
  • type: ref — неплохо, используется индекс.
  • key: idx_user — используется индекс по user_id.
  • rows: 50 — будет проверено около 50 строк.
  • Extra: Using where — условие status = 'shipped' применяется после доступа по индексу user_id.

Проблема: Индекс используется только по первому полю (user_id). Для 50 строк это может быть нормально, но если у пользователя тысячи заказов, фильтрация по status будет медленной.

Решение: Создать составной индекс, покрывающий оба поля:

CREATE INDEX idx_user_status ON orders(user_id, status);

После этого EXPLAIN может показать type: ref, key: idx_user_status, Extra: Using index (если запрос выбирает только эти два поля) — что является оптимальным планом.