Generalized Inverted Index для полнотекстового поиска, JSONB и массивов.
GIN — это инвертированный индекс для составных значений. Если B-дерево хранит «строка → ключ», то GIN хранит «ключ → список строк».
GIN (Generalized Inverted Index) — это инвертированный индекс для индексации составных значений, где одна колонка содержит множество элементов.
| Сценарий | Пример |
|---|---|
| Массивы | Поиск статей по тегам: WHERE 'postgresql' = ANY(tags) |
| JSONB | Поиск по полям JSON: WHERE data->>'status' = 'active' |
| Полнотекстовый поиск | Поиск документов по словам: WHERE text @@ to_tsquery('postgres') |
| tsvector | Векторизованный текст для полнотекстового поиска |
Обычный индекс (B-дерево):
TID → Ключ
(1) → 'hello world'
(2) → 'foo bar'
GIN-индекс:
Ключ → Список TID
'hello' → [1]
'world' → [1]
'foo' → [2]
'bar' → [2]
┌─────────────────────────────────────────┐
│ Posting Tree │
├─────────────────────────────────────────┤
│ Key: 'hello' → [TID1, TID2, TID3] │
│ Key: 'world' → [TID1, TID5] │
│ Key: 'foo' → [TID2, TID4] │
│ Key: 'bar' → [TID2] │
└─────────────────────────────────────────┘
Posting list — список TID для каждого ключа.
SELECT * FROM articles WHERE 'postgresql' = ANY(tags);'postgresql' в дереве[TID1, TID5, TID10]CREATE TABLE articles (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255),
tags TEXT[] -- Массив тегов
);
INSERT INTO articles (title, tags) VALUES
('PostgreSQL Guide', ARRAY['postgresql', 'database', 'sql']),
('Python Tips', ARRAY['python', 'programming']),
('Web Development', ARRAY['html', 'css', 'javascript']),
('Advanced PostgreSQL', ARRAY['postgresql', 'database', 'performance']);CREATE INDEX idx_articles_tags ON articles USING GIN (tags);-- Поиск по одному тегу
SELECT * FROM articles WHERE 'postgresql' = ANY(tags);
-- Или эквивалентно:
SELECT * FROM articles WHERE tags && ARRAY['postgresql'];
-- Поиск по нескольким тегам (пересечение)
SELECT * FROM articles WHERE tags @> ARRAY['postgresql', 'database'];
-- Поиск по любому из тегов (объединение)
SELECT * FROM articles WHERE tags && ARRAY['python', 'postgresql'];
-- Точное совпадение массива
SELECT * FROM articles WHERE tags = ARRAY['postgresql', 'database', 'sql'];| Оператор | Описание | Пример |
|---|---|---|
= | Точное совпадение | tags = ARRAY['a', 'b'] |
@> | Содержит | tags @> ARRAY['postgresql'] |
<@ | Содержится в | ARRAY['postgresql'] <@ tags |
&& | Пересекается | tags && ARRAY['python', 'sql'] |
ANY() | Любой элемент | 'postgresql' = ANY(tags) |
CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255),
attributes JSONB
);
INSERT INTO products (name, attributes) VALUES
('Laptop', '{"brand": "Apple", "price": 1500, "color": "silver"}'),
('Phone', '{"brand": "Samsung", "price": 800, "color": "black"}'),
('Tablet', '{"brand": "Apple", "price": 600, "color": "silver"}');CREATE INDEX idx_products_attributes ON products USING GIN (attributes);-- Поиск по ключу и значению
SELECT * FROM products WHERE attributes @> '{"brand": "Apple"}';
-- Поиск по нескольким полям
SELECT * FROM products
WHERE attributes @> '{"brand": "Apple", "color": "silver"}';
-- Поиск по вложенным полям
SELECT * FROM products
WHERE attributes->'specs' @> '{"ram": "16GB"}';
-- Поиск по массиву в JSON
SELECT * FROM products
WHERE attributes->'tags' @> '["wireless", "bluetooth"]';| Оператор | Описание | Пример |
|---|---|---|
@> | Содержит | jsonb @> '{"key": "value"}' |
<@ | Содержится в | '{"key": "value"}' <@ jsonb |
? | Имеет ключ | jsonb ? 'key' |
| `? | ` | Имеет любой ключ |
?& | Имеет все ключи | jsonb ?& ARRAY['key1', 'key2'] |
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255),
content TEXT
);
INSERT INTO documents (title, content) VALUES
('PostgreSQL Tutorial', 'Learn PostgreSQL database management'),
('Python Guide', 'Introduction to Python programming'),
('Advanced SQL', 'Advanced SQL queries and optimization');-- Индекс по tsvector
CREATE INDEX idx_docs_search ON documents
USING GIN (to_tsvector('english', title || ' ' || content));-- Поиск по слову
SELECT * FROM documents
WHERE to_tsvector('english', title || ' ' || content)
@@ to_tsquery('english', 'postgresql');
-- Поиск по нескольким словам (И)
SELECT * FROM documents
WHERE to_tsvector('english', title || ' ' || content)
@@ to_tsquery('english', 'database & management');
-- Поиск по нескольким словам (ИЛИ)
SELECT * FROM documents
WHERE to_tsvector('english', title || ' ' || content)
@@ to_tsquery('english', 'python | postgresql');-- Добавить хранимый столбец
ALTER TABLE documents
ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (to_tsvector('english', title || ' ' || content)) STORED;
-- Создать индекс по столбцу
CREATE INDEX idx_docs_search ON documents USING GIN (search_vector);
-- Запрос проще
SELECT * FROM documents WHERE search_vector @@ to_tsquery('english', 'postgresql');GIN поддерживает разные операторские классы для разных типов данных.
-- По умолчанию
CREATE INDEX idx ON table USING GIN (column);
-- Явно
CREATE INDEX idx ON table USING GIN (column gin_btree_ops);CREATE INDEX idx ON table USING GIN (column gin_trgm_ops);-- GIN с триграммами для LIKE и ILIKE
CREATE INDEX idx_users_name ON users USING GIN (name gin_trgm_ops);
-- Запросы
SELECT * FROM users WHERE name LIKE '%john%';
SELECT * FROM users WHERE name ILIKE '%JOHN%';
SELECT * FROM users WHERE name % 'jon'; -- Нечёткое совпадение-- B-дерево НЕ работает для массивов
CREATE INDEX idx_tags_btree ON articles USING BTREE (tags); -- Ошибка!
-- GIN работает
CREATE INDEX idx_tags_gin ON articles USING GIN (tags); -- ✅EXPLAIN ANALYZE
SELECT * FROM articles WHERE tags @> ARRAY['postgresql'];Без индекса:
Seq Scan on articles
Filter: (tags @> '{postgresql}'::text[])
Execution Time: 2.5 ms
С GIN:
Bitmap Heap Scan on articles
Recheck Cond: (tags @> '{postgresql}'::text[])
-> Bitmap Index Scan on idx_tags_gin
Index Cond: (tags @> '{postgresql}'::text[])
Execution Time: 0.3 ms
GIN-индексы обычно больше B-дерева:
| Тип данных | B-дерево | GIN |
|---|---|---|
| Массив из 5 элементов | N/A | ~3x от данных |
| JSONB с 10 полями | N/A | ~2x от данных |
| tsvector | N/A | ~1.5x от данных |
-- Включить быстрое обновление (по умолчанию on)
CREATE INDEX idx ON table USING GIN (column) WITH (fastupdate = on);
-- Отключить для стабильной производительности вставки
CREATE INDEX idx ON table USING GIN (column) WITH (fastupdate = off);fastupdate = on:
fastupdate = off:
-- Размер буфера для fastupdate (по умолчанию 4 MB)
SET gin_pending_list_limit = '8MB';-- Ограничение на количество результатов для нечёткого поиска
SET gin_fuzzy_search_limit = 100;SELECT
indexname,
pg_size_pretty(pg_relation_size(indexname::regclass)) as size
FROM pg_indexes
WHERE tablename = 'articles';SELECT
indexrelname,
idx_scan,
idx_tup_read
FROM pg_stat_user_indexes
WHERE relname = 'articles';-- Расширение pgstattuple
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT * FROM pgstatginindex('idx_articles_tags');CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255),
filters JSONB -- {"brand": "Apple", "color": "red", "size": "L"}
);
CREATE INDEX idx_products_filters ON products USING GIN (filters);
-- Поиск по нескольким фильтрам
SELECT * FROM products
WHERE filters @> '{"brand": "Apple", "color": "red"}';CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255),
tags TEXT[]
);
CREATE INDEX idx_posts_tags ON posts USING GIN (tags);
-- Поиск постов с определёнными тегами
SELECT * FROM posts WHERE tags @> ARRAY['postgresql'];
-- Поиск постов с любым из тегов
SELECT * FROM posts WHERE tags && ARRAY['postgresql', 'python'];CREATE TABLE articles (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255),
content TEXT,
search_vector tsvector
GENERATED ALWAYS AS (to_tsvector('russian', title || ' ' || content)) STORED
);
CREATE INDEX idx_articles_search ON articles USING GIN (search_vector);
-- Поиск с ранжированием
SELECT
id,
title,
ts_rank(search_vector, query) as rank
FROM articles, to_tsquery('russian', 'база & данных') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 10;Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.