Как понять, какой индекс нужен 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 может оказаться лишним, но это зависит от нагрузки.
Чек-лист
- Взять конкретный медленный SQL-запрос.
- Запустить EXPLAIN.
- Посмотреть key, rows и Extra.
- Определить поля WHERE, JOIN и ORDER BY.
- Продумать порядок колонок в составном индексе.
- Проверить leftmost prefix.
- Оценить кардинальность полей.
- Проверить, нет ли уже похожего индекса.
- Сравнить EXPLAIN до и после.
Индекс должен решать конкретную проблему конкретного запроса. Если после добавления индекса EXPLAIN не изменился или rows почти не уменьшился, индекс, скорее всего, выбран неправильно.