← Назад к вопросам
Как можно оптимизировать таблицу по двум полям с помощью индексов?
3.0 Senior🔥 111 комментариев
#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Оптимизация индексов по двум полям
Это очень частая задача. Нужно понимать, как работают составные индексы и когда их использовать.
Основная концепция: составной индекс
Для запросов по двум полям обычно нужен составной индекс (composite index):
-- Создать составной индекс
CREATE INDEX idx_user_status_created ON users(status, created_at);
Этот индекс эффективен для:
-- ✅ Работает идеально (оба поля в индексе)
SELECT * FROM users WHERE status = active AND created_at > 2024-01-01;
-- ✅ Работает (первое поле в индексе)
SELECT * FROM users WHERE status = active;
-- ❌ Неэффективно (пропущено первое поле)
SELECT * FROM users WHERE created_at > 2024-01-01;
Порядок полей в индексе — важен!
Для разных query паттернов нужны разные порядки:
-- Паттерн 1: Равенство + Диапазон
SELECT * FROM orders WHERE customer_id = 123 AND amount > 1000;
-- Оптимальный индекс:
CREATE INDEX idx_orders ON orders(customer_id, amount);
-- Паттерн 2: Два диапазона
SELECT * FROM transactions
WHERE created_at > 2024-01-01 AND amount BETWEEN 100 AND 5000;
-- Оптимальный индекс:
CREATE INDEX idx_transactions ON transactions(created_at, amount);
Правило ESR (Equality, Sort, Range)
Порядок полей в индексе должен быть:
- E — Equality (=) — поля с точным совпадением
- S — Sort — поля для сортировки
- R — Range (>, <, BETWEEN) — поля с диапазоном
-- Запрос:
SELECT * FROM users
WHERE role = admin -- Equality
AND department = IT -- Equality
AND created_at > 2024-01-01 -- Range
ORDER BY created_at; -- Sort
-- Оптимальный индекс:
CREATE INDEX idx_users ON users(role, department, created_at);
Практические примеры
Пример 1: Поиск + Фильтр по статусу
# ORM запрос (SQLAlchemy)
from sqlalchemy import and_
users = session.query(User).filter(
and_(
User.status == active,
User.created_at >= datetime(2024, 1, 1)
)
).all()
-- SQL
SELECT * FROM users
WHERE status = active AND created_at >= 2024-01-01;
-- Оптимальный индекс (Equality первым):
CREATE INDEX idx_users_status_created ON users(status, created_at);
Пример 2: Сортировка по одному полю с фильтром
# Получить последних активных пользователей
users = session.query(User).filter(
User.status == active
).order_by(User.created_at.desc()).limit(10)
SELECT * FROM users
WHERE status = active
ORDER BY created_at DESC
LIMIT 10;
-- Индекс (фильтр + сортировка)
CREATE INDEX idx_users_status_created_desc ON users(status, created_at DESC);
Пример 3: Два фильтра + Сортировка
orders = session.query(Order).filter(
and_(
Order.customer_id == customer_id,
Order.status.in_([pending, shipped])
)
).order_by(Order.created_at.desc())
SELECT * FROM orders
WHERE customer_id = 123
AND status IN (pending, shipped)
ORDER BY created_at DESC;
-- Индекс
CREATE INDEX idx_orders_customer_status_date
ON orders(customer_id, status, created_at DESC);
Когда нужны разные индексы
-- Запрос 1: Фильтр по customer_id и status
SELECT * FROM orders WHERE customer_id = 123 AND status = active;
-- CREATE INDEX idx1 ON orders(customer_id, status);
-- Запрос 2: Фильтр по status и date
SELECT * FROM orders WHERE status = active AND created_at > 2024-01-01;
-- CREATE INDEX idx2 ON orders(status, created_at);
-- Если оба запроса важны, может потребоваться два индекса
Использование COVERING INDEX (Include)
Если в SELECT есть поля, которых нет в WHERE, можно использовать covering index:
-- Запрос
SELECT id, customer_id, amount FROM orders
WHERE customer_id = 123 AND status = active;
-- Covering index (PostgreSQL 11+)
CREATE INDEX idx_orders_cover ON orders(customer_id, status)
INCLUDE (amount);
Анализ индексов — EXPLAIN
from sqlalchemy import text
# Проверить план выполнения
query = text("""
EXPLAIN ANALYZE
SELECT * FROM users
WHERE status = active AND created_at > 2024-01-01
""")
result = session.execute(query)
for row in result:
print(row)
Ищем в выводе:
- ✅
Index Scan— хорошо, индекс работает - ❌
Seq Scan— плохо, сканируется вся таблица - ❌
Index Condотсутствует — индекс не используется
Миграция с Goose
-- migrations/0025_add_user_indexes.sql
-- UP
CREATE INDEX idx_users_status_created ON users(status, created_at);
CREATE INDEX idx_orders_customer_status ON orders(customer_id, status);
-- DOWN (в секции после комментария)
-- +goose Down
DROP INDEX IF EXISTS idx_users_status_created;
DROP INDEX IF EXISTS idx_orders_customer_status;
Правила большого пальца
- Составной индекс лучше, чем два отдельных (в большинстве случаев)
- Порядок имеет значение — используй ESR
- Не переусложняй — слишком много индексов замедляет INSERT/UPDATE
- Используй EXPLAIN — никогда не угадывай
- Помни о NULL — индексы обрабатывают NULL, но с нюансами
- Избегай лишних индексов — они занимают место и замедляют мутации
Полезные запросы для аудита индексов
-- PostgreSQL: все индексы таблицы
SELECT indexname, indexdef FROM pg_indexes
WHERE tablename = 'users';
-- MySQL: информация об индексах
SHOW INDEX FROM users;
-- Размер индексов
SELECT schemaname, tablename, indexname, pg_size_pretty(pg_relation_size(indexrelid)) as size
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC;