<? phpukraine СПІВБЕСІДИ
Пошук по платформі
SQL · MIDDLE

Що таке складений індекс і чому важливий порядок колонок?

Складений індекс працює за принципом лівого префікса: індекс (a, b, c) допомагає умовам по a, по a і b, по a, b і c, але не по b чи c окремо.

Чи спрацює індекс (a, b) для умови лише по b?
Яку колонку ставити першою в індексі?
Коли краще два окремих індекси замість одного складеного?
індекси EXPLAIN

Складений індекс — це B-tree, у якому записи відсортовані спершу по першій колонці, всередині рівних значень по другій, і так далі. Звідси правило лівого префікса: індекс (a, b, c) дає швидкий пошук по a, по a, b і по a, b, c, але не по b окремо, бо значення b розкидані по всьому дереву. Телефонний довідник відсортований за прізвищем, потім за імʼям; знайти всіх Олен у ньому неможливо без повного перегляду.

Порядок колонок визначається запитами, які індекс має обслуговувати. Колонки з умовою рівності йдуть першими, колонка з діапазоном або та, по якій потрібне сортування, останньою. Після діапазону решта індексу для звуження пошуку вже не працює. Для типового WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT 20 правильний індекс (user_id, status, created_at): база стрибає в потрібне місце й читає двадцять уже відсортованих записів.

Індекс, який містить усі колонки запиту, стає покривним: таблиця не читається взагалі, у PostgreSQL це Index Only Scan із INCLUDE для додаткових колонок. Ціна кожного індексу — місце й повільніший запис, тому один складений індекс, спроєктований під кілька запитів, зазвичай кращий за набір вузьких. Після створення індексу його використання перевіряють через EXPLAIN, а не припускають.

-- Запит, під який проєктуємо індекс
SELECT id, total
FROM orders
WHERE user_id = 42 AND status = 'paid'
ORDER BY created_at DESC
LIMIT 20;

-- Правильно: рівності попереду, сортування останнім
CREATE INDEX orders_user_status_created ON orders (user_id, status, created_at DESC);

-- Неправильно: після діапазону або сортування решта індексу не використовується для пошуку
CREATE INDEX orders_created_user ON orders (created_at, user_id, status);

-- Покривний: total у INCLUDE, таблицю читати не треба (PostgreSQL)
CREATE INDEX orders_user_status_created_cov
    ON orders (user_id, status, created_at DESC) INCLUDE (total);

-- Перевірка: має бути Index Scan / Index Only Scan, а не Seq Scan + Sort
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, total FROM orders
WHERE user_id = 42 AND status = 'paid'
ORDER BY created_at DESC LIMIT 20;
Правило лівого префікса з поясненням через структуру B-tree: записи відсортовані спершу по a, всередині рівних a по b, тому без a стрибнути до потрібного b неможливо.
Що колонки з умовою рівності йдуть першими, а колонка з діапазоном або сортуванням останньою: після діапазону решта індексу для пошуку не використовується.
Що селективність важлива, але порядок визначає перш за все набір запитів, які індекс має покривати.
Що покривний індекс, який містить усі потрібні колонки, дозволяє не читати таблицю взагалі: Index Only Scan у PostgreSQL, Using index у MySQL.
Що кожен індекс коштує на запис і памʼять, тому один складений індекс під кілька запитів часто кращий за кілька вузьких.
Казати, що порядок не має значення й індекс працює для будь-якої комбінації.
Створювати індекс (created_at, user_id) для запиту WHERE user_id = ? ORDER BY created_at: діапазон або сортування мають бути в кінці.
Створювати окремі індекси по кожній колонці й очікувати, що база обʼєднає їх так само ефективно, як складений.
Ставити першою колонку з двома значеннями на кшталт is_active лише тому, що вона є в кожному запиті, не перевіривши альтернативи.
Не дивитись EXPLAIN після створення індексу й вірити, що він використовується.
ПОРАДА

Хороша ілюстрація — телефонний довідник: за прізвищем шукати легко, за одним лише імʼям — ні. І назвіть правило: рівність, потім діапазон або сортування.

оновлено 3 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Індекс (a, b) допомагає умовам по a та по a і b, але не по b окремо.