Как создать индекс в SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Базовый синтаксис
CREATE INDEX idx_name ON table_name (column_name);1. Индекс на одну колонку
CREATE INDEX idx_users_email ON users (email);Ускоряет:
WHERE email = 'x'ORDER BY emailJOIN ON users.email = ...
2. Composite (составной) индекс
CREATE INDEX idx_orders_user_date ON orders (user_id, created_at);Ускоряет:
WHERE user_id = 42(использует leftmost)WHERE user_id = 42 AND created_at > '...'(использует оба)WHERE created_at > '...'— НЕ использует (нет leftmost)
Правило leftmost: индекс используется, если в WHERE фигурируют колонки слева направо.
3. UNIQUE индекс
CREATE UNIQUE INDEX idx_users_email ON users (email);То же, что UNIQUE constraint: нет дубликатов + ускоряет поиск.
4. Partial (частичный) индекс
Индекс только на подмножество строк:
-- Postgres
CREATE INDEX idx_orders_paid
ON orders (user_id)
WHERE status = 'paid';Меньше размер → быстрее.
5. Functional (выражение-индекс)
Для case-insensitive поиска:
CREATE INDEX idx_users_email_lower
ON users (LOWER(email));
-- теперь это использует индекс
SELECT * FROM users WHERE LOWER(email) = 'alice@...';6. Индекс по несколько типов
B-tree (по умолчанию)
Для сравнений =, <, >, BETWEEN, ORDER BY.
CREATE INDEX idx_a ON orders (created_at);Hash
Только для равенства =. Обычно не быстрее B-tree.
CREATE INDEX idx_b ON users USING HASH (email);GIN — для массивов, JSON, text search
CREATE INDEX idx_tags ON users USING GIN (tags);Ускоряет:
WHERE tags @> ARRAY['vip']GiST — для геоданных, ranges
CREATE INDEX idx_range ON events USING GIST (event_range);BRIN — для огромных таблиц с упорядоченными данными
CREATE INDEX idx_time ON events USING BRIN (created_at);Маленький по размеру, быстрый для временных рядов.
7. Covering index (INCLUDE)
Postgres 11+:
CREATE INDEX idx_orders_covering
ON orders (user_id)
INCLUDE (total, status);Запрос может вернуть total, status только из индекса (Index Only Scan) — не обращаясь к таблице.
8. Удалить индекс
DROP INDEX idx_name;9. Посмотреть существующие индексы
Postgres
SELECT * FROM pg_indexes WHERE tablename = 'users';MySQL
SHOW INDEX FROM users;10. Когда НЕ создавать индекс
- Маленькие таблицы (< 1000 строк) — seq scan и так быстрый
- Часто обновляемые колонки — индекс тоже обновляется
- Низкая селективность (boolean, gender) — индекс не даст выигрыша
Compact guide: какой индекс для чего
| Запрос | Индекс |
|---|---|
WHERE col = x |
B-tree на col |
WHERE col BETWEEN a AND b |
B-tree на col |
ORDER BY col |
B-tree на col |
JOIN ON a.col = b.col |
B-tree на обе стороны |
WHERE col @> array['x'] |
GIN |
WHERE JSONB_col ->> 'key' = x |
GIN or functional |
WHERE LOWER(col) = x |
functional LOWER(col) |
| Highly selective + static filter | partial index |
Производительность
- Создание индекса на большой таблице — блокировка. В Postgres —
CREATE INDEX CONCURRENTLY(не блокирует запись). - Индекс занимает место: обычно 10-30% от размера таблицы.
- Лишний индекс замедляет INSERT / UPDATE.
Частые ошибки
Индекс на bool
Низкая селективность → толку мало.
Индекс на WHERE функцию
WHERE LOWER(col) = x — обычный индекс не работает. Нужен functional.
Много индексов на OLTP-таблице
Каждый INSERT/UPDATE перестраивает всё. Держите только нужные.
Не использовать leftmost
Composite (a, b) не ускоряет WHERE b = x. Нужен отдельный индекс на b.
Связанные темы
FAQ
Сколько индексов на таблицу — нормально?
3-10 для OLTP. Больше — замедляет запись.
B-tree или hash?
B-tree почти всегда. Hash только когда нужен строгий = без >, <.
Когда partial index?
Когда запрос часто использует конкретное условие (WHERE status = 'active').
CONCURRENTLY или обычное создание?
На prod — CONCURRENTLY. Не блокирует запись.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.