Индексы с WHERE-условием: экономия места и оптимизация специфичных запросов.
Частичный индекс содержит только подмножество строк таблицы. Это экономия места и ускорение специфичных запросов.
Частичный индекс (partial index) — это индекс с WHERE-условием, содержащий только строки, удовлетворяющие этому условию.
-- Полный индекс (все строки)
CREATE INDEX idx_users_email ON users(email);
-- Частичный индекс (только активные пользователи)
CREATE INDEX idx_users_active_email ON users(email) WHERE active = true;| Преимущество | Описание |
|---|---|
| Меньший размер | Индекс содержит только часть строк |
| Быстрее поиск | Меньше записей для сканирования |
| Быстрее вставка | Индекс обновляется только для подходящих строк |
| Специфичная оптимизация | Для редких, но важных запросов |
CREATE INDEX index_name ON table_name (column) WHERE predicate;-- Индекс для активных пользователей
CREATE INDEX idx_active_users ON users(email) WHERE active = true;
-- Индекс для заказов со статусом 'pending'
CREATE INDEX idx_pending_orders ON orders(user_id, created_at)
WHERE status = 'pending';
-- Индекс для больших заказов
CREATE INDEX idx_large_orders ON orders(user_id) WHERE amount > 10000;
-- Индекс для не удалённых записей
CREATE INDEX idx_not_deleted ON documents(title) WHERE deleted_at IS NULL;-- Статусы заказов: 'pending' (5%), 'completed' (70%), 'cancelled' (25%)
CREATE INDEX idx_pending_orders ON orders(user_id, created_at)
WHERE status = 'pending';Почему:
status = 'pending' — только 5% строк-- Таблица с мягким удалением (soft delete)
CREATE TABLE documents (
id BIGSERIAL,
title VARCHAR(255),
content TEXT,
deleted_at TIMESTAMP
);
-- Индекс только для активных документов
CREATE INDEX idx_active_docs ON documents(title, created_at)
WHERE deleted_at IS NULL;Почему:
-- Индекс для премиум-заказов (amount > 10000)
CREATE INDEX idx_premium_orders ON orders(user_id, amount)
WHERE amount > 10000;Почему:
-- Уникальный email только для активных пользователей
CREATE UNIQUE INDEX idx_unique_active_email ON users(email)
WHERE active = true;Результат:
Оптимизатор использует частичный индекс, только если запрос логически подразумевает предикат.
CREATE INDEX idx_pending ON orders(user_id) WHERE status = 'pending';
-- ✅ Использует индекс
SELECT * FROM orders WHERE status = 'pending' AND user_id = 123;
-- ✅ Использует индекс (status IN включает 'pending')
SELECT * FROM orders WHERE status IN ('pending', 'completed') AND user_id = 123;
-- Но может не использовать, если селективность низкая
-- ❌ НЕ использует индекс
SELECT * FROM orders WHERE user_id = 123;
-- Нет условия на status
-- ❌ НЕ использует индекс
SELECT * FROM orders WHERE status = 'completed' AND user_id = 123;
-- Предикат не выполняется (completed ≠ pending)Предикат может быть сложным:
CREATE INDEX idx_recent_active ON orders(user_id, created_at)
WHERE status = 'pending' AND created_at > '2026-01-01';
-- ✅ Использует индекс
SELECT * FROM orders
WHERE status = 'pending'
AND created_at > '2026-02-01' -- Подмножество предиката
AND user_id = 123;
-- ❌ НЕ использует индекс
SELECT * FROM orders
WHERE status = 'pending'
AND created_at > '2025-01-01' -- Более широкий диапазон
AND user_id = 123;-- Полный индекс
CREATE INDEX idx_all_orders ON orders(user_id, status);
-- Размер: 50 MB
-- Частичный индекс
CREATE INDEX idx_pending_orders ON orders(user_id) WHERE status = 'pending';
-- Размер: 2 MB (только 5% строк)EXPLAIN ANALYZE
SELECT * FROM orders WHERE status = 'pending' AND user_id = 123;Полный индекс:
Index Scan using idx_all_orders on orders
Index Cond: (user_id = 123 AND status = 'pending')
Execution Time: 0.5 ms
Частичный индекс:
Index Scan using idx_pending_orders on orders
Index Cond: (user_id = 123)
Execution Time: 0.1 ms
Ускорение: в 5 раз (меньше записей для сканирования)
INSERT INTO orders (user_id, status, amount) VALUES (456, 'completed', 100);Полный индекс:
Частичный индекс:
-- Один активный заказ на пользователя в момент времени
CREATE UNIQUE INDEX idx_one_active_per_user ON orders(user_id)
WHERE status IN ('pending', 'processing');Результат:
-- Уникальный slug для опубликованных статей
CREATE UNIQUE INDEX idx_published_slug ON articles(slug)
WHERE published = true;
-- Черновики могут иметь дубликаты slug
INSERT INTO articles (slug, published) VALUES ('my-post', false); -- ✅
INSERT INTO articles (slug, published) VALUES ('my-post', false); -- ✅
INSERT INTO articles (slug, published) VALUES ('my-post', true); -- ✅ (первый published)
INSERT INTO articles (slug, published) VALUES ('my-post', true); -- ❌ ОшибкаCREATE INDEX idx_composite_partial ON orders(user_id, created_at DESC)
WHERE status = 'pending';CREATE INDEX idx_covering_partial ON orders(user_id)
INCLUDE (created_at, amount)
WHERE status = 'pending';CREATE INDEX idx_lower_email_active ON users(LOWER(email))
WHERE active = true;SELECT
indexname,
pg_size_pretty(pg_relation_size(indexname::regclass)) as size,
indexdef
FROM pg_indexes
WHERE tablename = 'orders'
AND indexdef LIKE '%WHERE%';SELECT
indexrelname,
idx_scan as scans,
idx_tup_read as tuples_read,
pg_size_pretty(pg_relation_size(indexrelid)) as size
FROM pg_stat_user_indexes
WHERE relname = 'orders';SELECT
indexname,
pg_get_indexdef(indexrelid) as definition
FROM pg_indexes
WHERE schemaname = 'public'
AND indexdef LIKE '%WHERE%';-- ❌ Плохо: дублирование индекса
CREATE INDEX idx_all ON orders(user_id);
CREATE INDEX idx_pending ON orders(user_id) WHERE status = 'pending';
-- Второй индекс покрывает подмножество первого, но первый всё ещё нужен для других статусов
-- ✅ Хорошо: только частичный, если запросы только к pending
CREATE INDEX idx_pending ON orders(user_id) WHERE status = 'pending';-- ❌ Бесполезно: 99% строк подходят
CREATE INDEX idx_mostly_all ON orders(user_id)
WHERE created_at > '2020-01-01'; -- Почти все записи
-- ✅ Хорошо: 5-10% строк
CREATE INDEX idx_pending ON orders(user_id)
WHERE status = 'pending';-- ❌ Не сработает как ожидается
CREATE INDEX idx_lower_status ON orders(user_id)
WHERE LOWER(status) = 'pending';
-- Запрос:
SELECT * FROM orders WHERE LOWER(status) = 'pending'; -- ✅ Работает
SELECT * FROM orders WHERE status = 'pending'; -- ❌ НЕ работает!
-- ✅ Правильно
CREATE INDEX idx_status ON orders(user_id)
WHERE status = 'pending';-- ❌ Предикат должен быть статичным
CREATE INDEX idx_recent ON orders(user_id)
WHERE created_at > NOW() - INTERVAL '30 days'; -- Ошибка!
-- ✅ Правильно: статичное значение
CREATE INDEX idx_recent ON orders(user_id)
WHERE created_at > '2026-01-01';pg_relation_size()CREATE TABLE tasks (
id BIGSERIAL,
queue_name VARCHAR(50),
status VARCHAR(20), -- 'pending', 'running', 'completed', 'failed'
payload JSONB,
created_at TIMESTAMP
);
-- Индекс только для pending задач (очередь)
CREATE INDEX idx_tasks_pending ON tasks(queue_name, created_at)
WHERE status = 'pending';
-- Выбор следующей задачи
SELECT * FROM tasks
WHERE queue_name = 'emails' AND status = 'pending'
ORDER BY created_at ASC
LIMIT 1;CREATE TABLE sessions (
id BIGSERIAL,
user_id BIGINT,
is_active BOOLEAN,
last_seen TIMESTAMP
);
-- Индекс только для активных сессий
CREATE INDEX idx_active_sessions ON sessions(user_id, last_seen)
WHERE is_active = true;
-- Поиск активных сессий пользователя
SELECT * FROM sessions
WHERE user_id = 123 AND is_active = true
ORDER BY last_seen DESC;CREATE TABLE events (
id BIGSERIAL,
event_type VARCHAR(50),
event_data JSONB,
archived BOOLEAN DEFAULT false,
created_at TIMESTAMP
);
-- Индекс для активных (не архивных) событий
CREATE INDEX idx_active_events ON events(event_type, created_at)
WHERE archived = false;
-- Отдельный индекс для архива (если нужен доступ)
CREATE INDEX idx_archived_events ON events(event_type, created_at)
WHERE archived = true;Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.