Індекси PostgreSQL: B-tree, GIN і коли вони допомагають
Коротко: Індекс — додаткове дерево пошуку, що прискорює запити, але коштує запису та сховища; ви створюєте його свідомо для частих фільтрів, сортувань і з’єднань.
Докладніше: Без індексу PostgreSQL читає для запиту всю таблицю (seq scan). Індекс швидко знаходить рядки, як предметний покажчик у книзі.
Подробиці
Типи індексів
- B-tree (за замовчуванням): рівність, діапазони (
<,>,BETWEEN), сортування (ORDER BY), префіксLIKE 'abc%'. - Hash: лише рівність.
- GIN: багатозначні дані (масиви, JSONB, повнотекстовий
tsvector). - GiST/SP-GiST: геодані, діапазони, схожість.
- BRIN: дуже великі, впорядковані за часом таблиці, крихітний.
- pgvector (HNSW/IVFFlat): векторний пошук.
CREATE INDEX idx_user_email ON users (email);
CREATE UNIQUE INDEX uq_user_email ON users (lower(email));
CREATE INDEX idx_order_customer_date ON orders (customer_id, created DESC);
CREATE INDEX idx_active ON users (id) WHERE active; -- частковий
CREATE INDEX idx_tags ON articles USING gin (tags);Коли індекс окуповується?
- Стовпці в WHERE, JOIN (зовнішні ключі!), ORDER BY.
- Вибіркові умови (мало збігів).
- Складені індекси: порядок важливий;
(customer_id, created)допомагає заWHERE customer_id = …і… AND created …, але не за фільтра лише заcreated(правило лівого префікса). - Без індексу на крихітних таблицях, на стовпцях із малою кількістю значень (так/ні) самих по собі або якщо в таблицю постійно інтенсивно пишуть.
- Вартість: кожен індекс сповільнює
INSERT/UPDATE/DELETE, займає сховище та обслуговування.
Перевірка через EXPLAIN
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE customer_id = 42 ORDER BY created DESC LIMIT 10;Seq Scan на великій таблиці = підозра; Index Scan/Index Only Scan/Bitmap Heap Scan = індекс використано. Планувальник обирає за статистикою (ANALYZE); іноді за багатьох збігів seq scan швидший. pg_stat_user_indexes показує ніколи не використовувані індекси (видаліть їх). CREATE INDEX CONCURRENTLY будує без довгого блокування.
Типові помилки
Функції над стовпцями (WHERE lower(email) = …) заважають звичайному індексу (→ функціональний індекс). Перетворення типів, провідний % у LIKE, OR за різними стовпцями. Відсутність індексу на зовнішньому ключі сповільнює з’єднання та видалення. Забагато індексів. У безсерверних базах даних враховується й час обчислень: менше seq scan = менше годин CPU.
Див. також: PostgreSQL, Планувальник запитів і EXPLAIN, Оптимізація запитів з EXPLAIN, Проблема N+1, Векторні бази даних