Складений індекс (a, b, c) - це B-дерево, впорядковане спершу за a, всередині однакових a - за b, всередині однакових (a, b) - за c. Як телефонний довідник: прізвище, потім ім'я.
Правило лівого префікса: індекс можна використати для пошуку лише за початковими колонками:
CREATE INDEX orders_idx ON orders (user_id, status, created_at);
-- індекс працює
WHERE user_id = 7
WHERE user_id = 7 AND status = 'paid'
WHERE user_id = 7 AND status = 'paid' AND created_at > '2026-01-01'
WHERE status = 'paid' AND user_id = 7 -- порядок в умові не важливий
-- індекс для пошуку не працює
WHERE status = 'paid' -- пропущено user_id
WHERE created_at > '2026-01-01'
Знайти всіх Іванів у довіднику, впорядкованому за прізвищем, можна лише переглянувши його повністю.
Діапазон «обриває» індекс. Колонки після першої з діапазоном (>, <, BETWEEN, LIKE 'abc%') для пошуку вже не використовуються:
WHERE user_id = 7 AND created_at > '2026-01-01' AND status = 'paid'
-- індекс (user_id, created_at, status): пошук за user_id + діапазон created_at,
-- status перевіряється для кожного запису діапазону
-- індекс (user_id, status, created_at): пошук за всіма трьома - краще
Звідси практичне правило порядку колонок: спершу колонки з рівністю, потім колонка з діапазоном чи сортуванням.
Що ще дає порядок колонок:
ORDER BYза колонками, що йдуть одразу після колонок з рівністю, не потребує сортування:WHERE user_id = 7 ORDER BY created_atз індексом(user_id, created_at);- окремий індекс
(user_id)не потрібен, якщо є(user_id, status)- лівий префікс його заміняє. Це зайвий індекс, що лише сповільнює запис.
Skip scan (MySQL 8.0.13+) частково обходить правило: якщо в першої колонки мало різних значень, а запит покривається індексом, MySQL може «перестрибувати» по значеннях першої колонки. У EXPLAIN видно Using index for skip scan. Розраховувати на це при проєктуванні не варто.
У Laravel: $table->index(['user_id', 'status', 'created_at']); - порядок у масиві і є порядком колонок в індексі.