Как понять, какой индекс нужен MySQL-запросу

Индекс в MySQL нужен не “на всякий случай”, а под конкретный запрос. Если добавить индекс не на те поля или не в том порядке, он может почти не помочь. Если добавить слишком много индексов, запись в таблицу станет тяжелее, а поддержка базы сложнее.

Выбор индекса начинается с фактического SQL-запроса и EXPLAIN.

Начать с реального запроса

Нельзя проектировать индекс только по названию таблицы. Нужен запрос, который тормозит.

SELECT id, name, price
FROM products
WHERE supplier_id = 10
  AND status = 'A'
ORDER BY updated_at DESC
LIMIT 50;

Для такого запроса важны supplier_id, status и updated_at. Но порядок в индексе нужно выбирать с учётом фильтрации и сортировки.

EXPLAIN

EXPLAIN показывает, как MySQL планирует выполнить запрос.

EXPLAIN
SELECT id, name, price
FROM products
WHERE supplier_id = 10
  AND status = 'A'
ORDER BY updated_at DESC
LIMIT 50;

В первую очередь смотрят:

  • type;
  • possible_keys;
  • key;
  • rows;
  • Extra.

Если key пустой, индекс не используется. Если rows слишком большой, база просматривает много строк.

Составной индекс

Для примера выше может подойти индекс:

CREATE INDEX idx_products_supplier_status_updated
ON products (supplier_id, status, updated_at);

Он помогает отфильтровать поставщика и статус, а затем взять строки в нужном порядке. Но если запросы часто отличаются, индекс нужно проверять на реальных вариантах.

Leftmost prefix

MySQL использует составной индекс слева направо. Индекс на (supplier_id, status, updated_at) хорошо подходит для условий по supplier_id или supplier_id + status. Но не так полезен для запроса только по status.

-- индекс может использоваться нормально
WHERE supplier_id = 10 AND status = 'A'

-- индекс хуже подходит
WHERE status = 'A'

Поэтому порядок колонок в составном индексе важен.

WHERE и ORDER BY

Если запрос фильтрует и сортирует, хороший индекс может помочь и там, и там. Но это зависит от условий. Диапазонные условия могут ограничить дальнейшее использование индекса.

WHERE supplier_id = 10
  AND created_at >= '2026-06-01'
ORDER BY status

После диапазона по created_at сортировка по status может уже не использоваться так, как ожидается. Проверять нужно через EXPLAIN, а не предполагать.

JOIN

Для JOIN индексы нужны на полях соединения. Если orders.customer_id соединяется с customers.id, customer_id должен быть индексирован, особенно на большой таблице заказов.

SELECT o.id, c.name
FROM orders o
INNER JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'new';
CREATE INDEX idx_orders_status_customer
ON orders (status, customer_id);

Если status хорошо фильтрует данные, такой индекс может быть полезен. Если почти все строки имеют status = new, пользы будет меньше.

Кардинальность

Индекс по полю с двумя значениями не всегда полезен. Например, status может иметь A/N, и если активных товаров 95%, индекс по одному status почти ничего не отфильтрует.

SHOW INDEX FROM products;

Кардинальность показывает примерную уникальность значений. Чем ниже избирательность, тем осторожнее нужно оценивать пользу индекса.

Лишние индексы

Каждый индекс ускоряет чтение определённых запросов, но замедляет INSERT, UPDATE и DELETE. Если таблица активно обновляется, лишние индексы могут вредить.

Перед добавлением нового индекса нужно проверить, нет ли уже похожего. Индекс (supplier_id, status, updated_at) частично покрывает запросы по supplier_id и supplier_id + status. Отдельный индекс только supplier_id может оказаться лишним, но это зависит от нагрузки.

Чек-лист

  1. Взять конкретный медленный SQL-запрос.
  2. Запустить EXPLAIN.
  3. Посмотреть key, rows и Extra.
  4. Определить поля WHERE, JOIN и ORDER BY.
  5. Продумать порядок колонок в составном индексе.
  6. Проверить leftmost prefix.
  7. Оценить кардинальность полей.
  8. Проверить, нет ли уже похожего индекса.
  9. Сравнить EXPLAIN до и после.

Индекс должен решать конкретную проблему конкретного запроса. Если после добавления индекса EXPLAIN не изменился или rows почти не уменьшился, индекс, скорее всего, выбран неправильно.

Рассчитать Бесплатный аудит