← Назад к вопросам
Чем полезны для производительности составные индексы?
2.0 Middle🔥 171 комментариев
#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Составные индексы и производительность
Составные индексы (Composite Indexes или Multi-Column Indexes) — это индексы, построенные на нескольких колонках одновременно. Они критически важны для оптимизации сложных запросов.
1. Основная концепция
Составной индекс создаёт структуру для быстрого поиска по нескольким полям одновременно:
-- Простой индекс (одна колонка)
CREATE INDEX idx_email ON users(email);
-- Составной индекс (несколько колонок)
CREATE INDEX idx_users_email_status ON users(email, status);
-- Индекс на 3+ колонках
CREATE INDEX idx_orders_user_status_date ON orders(user_id, status, created_at);
Второй индекс полезен для запросов вроде:
-- Быстро найдёт пользователя по email И status благодаря составному индексу
SELECT * FROM users WHERE email = 'john@example.com' AND status = 'active';
-- Также сработает для части условий (prefix search)
SELECT * FROM users WHERE email = 'john@example.com';
-- Но для этого индекс уже НЕ поможет (reverse order)
SELECT * FROM users WHERE status = 'active';
2. B-Tree структура составного индекса
Построение индекса:
InDEX (email, status)
Создаёт сортировку:
- (alice@example.com, active)
- (alice@example.com, inactive)
- (bob@example.com, active)
- (bob@example.com, inactive)
- (charlie@example.com, active)
Примечание: первая колонка (email) сортируется основная,
вторая (status) сортирует внутри каждого значения email
3. Примеры использования и оптимизация
Сценарий 1: Фильтрация по нескольким полям
-- Таблица логов
CREATE TABLE logs (
id SERIAL PRIMARY KEY,
user_id INT,
event_type VARCHAR(50),
severity VARCHAR(20),
created_at TIMESTAMPTZ,
message TEXT
);
-- Плохо: три отдельных индекса
CREATE INDEX idx_user_id ON logs(user_id);
CREATE INDEX idx_event_type ON logs(event_type);
CREATE INDEX idx_severity ON logs(severity);
-- Хорошо: один составной индекс
CREATE INDEX idx_logs_composite ON logs(user_id, event_type, severity);
-- Запрос использует весь индекс
SELECT COUNT(*) FROM logs
WHERE user_id = 123
AND event_type = 'error'
AND severity = 'critical';
Сценарий 2: Сортировка и фильтрация
-- Таблица заказов
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID,
status VARCHAR(20),
total_amount DECIMAL(10, 2),
created_at TIMESTAMPTZ
);
-- Составной индекс для этого запроса
CREATE INDEX idx_orders_user_status_date ON orders(user_id, status, created_at DESC);
-- Запрос использует индекс для поиска И сортировки
SELECT * FROM orders
WHERE user_id = 'user-123' AND status = 'completed'
ORDER BY created_at DESC
LIMIT 10;
Без индекса:
- Сканирование всей таблицы O(n)
- Сортировка результатов в памяти (дорого)
С индексом:
- Прямой доступ к отсортированным данным O(log n)
- Возврат уже отсортированных результатов
4. Правило: Порядок колонок в индексе
Порядок критически важен! Это правило называется Equality-Range-Sort (ERS):
-- Хороший порядок колонок
CREATE INDEX idx_good ON orders(user_id, status, created_at);
-- Equality Range Sort
-- ↓ ↓ ↓
-- Запросы, которые будут быстрыми:
-- 1. WHERE user_id = ? AND status = ? ORDER BY created_at
-- ✓ Использует весь индекс
-- 2. WHERE user_id = ? AND status = ?
-- ✓ Использует первые две колонки
-- 3. WHERE user_id = ?
-- ✓ Использует первую колонку (prefix search)
-- 4. WHERE user_id = ? AND created_at > ?
-- ✗ Индекс не поможет с created_at (status пропущен)
Плохой порядок:
CREATE INDEX idx_bad ON orders(created_at, status, user_id);
-- Запрос WHERE user_id = ? не может использовать начало индекса
-- Пришлось бы сканировать весь индекс (неэффективно)
5. Пример в PostgreSQL
-- Реальный пример: интернет-магазин
CREATE TABLE products (
id UUID PRIMARY KEY,
category_id UUID NOT NULL,
brand_id UUID NOT NULL,
price DECIMAL(10, 2),
in_stock BOOLEAN,
created_at TIMESTAMPTZ,
rating FLOAT
);
CREATE TABLE reviews (
id UUID PRIMARY KEY,
product_id UUID REFERENCES products(id),
user_id UUID,
rating INT,
created_at TIMESTAMPTZ
);
-- Составной индекс для поиска товаров по категории, наличию и цене
CREATE INDEX idx_products_category_stock_price
ON products(category_id, in_stock, price);
-- Составной индекс для отзывов
CREATE INDEX idx_reviews_product_user_date
ON reviews(product_id, user_id, created_at DESC);
-- Быстрый запрос благодаря индексу
SELECT p.* FROM products p
WHERE p.category_id = 'cat-123'
AND p.in_stock = true
AND p.price BETWEEN 100 AND 500
ORDER BY p.created_at DESC
LIMIT 20;
6. Пример на SQLAlchemy + Python
from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, ForeignKey, create_engine, Index
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
from datetime import datetime
from sqlalchemy.dialects.postgresql import UUID
import uuid
Base = declarative_base()
class Product(Base):
__tablename__ = 'products'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
category_id = Column(UUID(as_uuid=True), nullable=False)
brand_id = Column(UUID(as_uuid=True), nullable=False)
price = Column(Float)
in_stock = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
# Составной индекс
__table_args__ = (
Index('idx_products_category_stock_price', 'category_id', 'in_stock', 'price'),
Index('idx_products_brand_stock', 'brand_id', 'in_stock'),
)
class Review(Base):
__tablename__ = 'reviews'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
product_id = Column(UUID(as_uuid=True), ForeignKey('products.id'))
user_id = Column(UUID(as_uuid=True))
rating = Column(Integer)
created_at = Column(DateTime, default=datetime.utcnow)
__table_args__ = (
Index('idx_reviews_product_rating', 'product_id', 'rating'),
)
# Использование в запросах
engine = create_engine('postgresql://user:password@localhost/db')
with Session(engine) as session:
# Запрос использует составной индекс
products = session.query(Product).filter(
Product.category_id == 'cat-123',
Product.in_stock == True,
Product.price >= 100,
Product.price <= 500
).order_by(Product.created_at.desc()).limit(20).all()
# Проверка плана выполнения
from sqlalchemy import text
result = session.execute(text("""
EXPLAIN ANALYZE
SELECT * FROM products
WHERE category_id = 'cat-123'
AND in_stock = true
AND price BETWEEN 100 AND 500
ORDER BY created_at DESC
LIMIT 20
"""))
for row in result:
print(row)
7. Анализ производительности
-- Проверка плана выполнения без индекса
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE user_id = 'user-123' AND status = 'completed';
-- Результат БЕЗ индекса:
-- Seq Scan on orders (cost=0.00..35000.00 rows=100000)
-- Filter: (user_id = 'user-123' AND status = 'completed')
-- Создаём индекс
CREATE INDEX idx_orders_user_status ON orders(user_id, status);
-- Результат С индексом:
-- Bitmap Index Scan using idx_orders_user_status (cost=0.00..100.00 rows=50)
-- Index Cond: (user_id = 'user-123' AND status = 'completed')
-- Улучшение: ~350x быстрее!
8. Минусы составных индексов
- Занимают место на диске — индекс = дополнительные данные
- Замедляют INSERT/UPDATE/DELETE — нужно обновлять индекс
- Сложность выбора — нужно выбрать правильный порядок колонок
- Переиспользование — избыток индексов = медленнее всё
9. Когда создавать составные индексы
-- Измеряй:
-- 1. Частота запроса
-- 2. Размер таблицы (маленькие таблицы < 10k строк не нуждаются в индексах)
-- 3. Селективность (уникальность значений)
-- Индексируй только горячие запросы
-- Убирай неиспользуемые индексы
-- Найти неиспользуемые индексы в PostgreSQL
SELECT schemaname, tablename, indexname
FROM pg_indexes
WHERE idx_scan = 0
ORDER BY idx_size DESC;
Резюме
Составные индексы улучшают производительность:
- Многоколончатые WHERE условия — ускорение в 100-1000x
- Сортировка — использование отсортированного индекса
- Covering индексы — все данные в индексе (zero disk reads)
- Prefix search — использование левых колонок индекса
Ключевое правило: Equality-Range-Sort (ERS) — порядок колонок в индексе критичен!