Индексы — важнейший инструмент оптимизации запросов в базах данных. В PostgreSQL одним из вариантов является хеш-индекс. В отличие от B-tree, он работает исключительно с операциями равенства (=) и использует бакеты для хранения ссылок на строки таблицы. Давайте разберёмся, как PostgreSQL управляет этими бакетами, какие особенности у хеш-индекса и в каких случаях его применение оправдано.
Что такое бакеты в хеш-индексе PostgreSQL?
При создании хеш-индекса PostgreSQL применяет хеш-функцию к каждому значению индексируемого столбца. Результат хеширования определяет, в какой бакет (bucket) попадёт запись.
📌 Формула назначения бакета: bucket_number = hash_value mod num_buckets
Это означает, что PostgreSQL делит хеш-значение на количество бакетов и использует остаток от деления для распределения данных.
Сравнение хеш-индекса и B-tree в цифрах
Рассмотрим производительность индексов на таблицах разного размера:
|
Количество записей |
Тип индекса |
Время вставки |
Время поиска (=) |
Время поиска (диапазон) |
|---|---|---|---|---|
|
100 тыс. |
B-tree |
0.2 сек |
1.1 мс |
2.0 мс |
|
Хеш-индекс |
0.15 сек |
0.8 мс |
❌ |
|
|
1 млн. |
B-tree |
1.2 сек |
3.5 мс |
5.2 мс |
|
Хеш-индекс |
0.9 сек |
2.1 мс |
❌ |
|
|
10 млн. |
B-tree |
13.5 сек |
10.8 мс |
18.2 мс |
|
Хеш-индекс |
9.1 сек |
7.4 мс |
❌ |
|
|
50 млн. |
B-tree |
65.2 сек |
42.3 мс |
79.5 мс |
|
Хеш-индекс |
47.8 сек |
31.6 мс |
❌ |
🔹 Вывод: B-tree выигрывает при поиске по диапазону (<, >, BETWEEN). Однако, если запросы используют только =, хеш-индекс оказывается быстрее, особенно на больших объёмах данных.
Как бакеты хранят данные?
Каждый бакет содержит страницы размером 8 Кб. В одной странице хранятся:
-
Заголовок (≈ 24 байта)
-
Хеш-значения ключей (по 4 байта)
-
Указатели на строки таблицы (tuple ID, TID) (по 6 байт)
⚠️ Важно: хеш-индекс хранит не ключ, а только его хеш-значение (в отличие от B-Tree).
Тогда на одной странице можно разместить (8Кб — 24б) / (4б + 6б) ≈ 800 записей.
Что происходит, если бакет переполняется?
Если количество записей в бакете превышает размер одной страницы, PostgreSQL создаёт страницы переполнения (overflow pages). Это замедляет работу индекса, так как поиск требует сканирования нескольких страниц.
📌 Оптимальный сценарий:
✔️ Значения равномерно распределены по бакетам.
✔️ Коллизии хеш-функции минимальны.
✔️ Доступ выполняется в O(1) (константное время).
📌 Проблемный сценарий:
❌ Записи с одинаковыми значениями хешируются в один бакет.
❌ Создаются страницы переполнения → поиск замедляется.
❌ PostgreSQL вынужден сканировать длинные цепочки записей.
Как проверить состояние хеш-индекса?
PostgreSQL не предоставляет встроенных средств просмотра количества бакетов, но можно воспользоваться расширением pageinspect:
CREATE EXTENSION IF NOT EXISTS pageinspect; SELECT * FROM hash_page_stats('my_hash_index', 0);
Также можно проверить статистику использования индекса:
SELECT indexrelid::regclass, idx_scan, idx_tup_read, idx_tup_fetch FROM pg_stat_user_indexes WHERE indexrelid = 'my_hash_index'::regclass;
Если idx_scan (количество использований) низкое, а idx_tup_read (количество прочитанных строк) высокое, это может указывать на переполнение бакетов.
Когда стоит (и не стоит) использовать хеш-индекс?
✔️ Подходит, если:
-
Данные размером от 100 тыс. до 50 млн. строк.
-
Только операции
=, без диапазонных запросов. -
Равномерное распределение значений (минимальные коллизии).
-
Запросы выполняются очень часто (например, кэш-таблица).
❌ Не подходит, если:
-
Менее 100 тыс. записей → PostgreSQL и так быстро сканирует таблицу.
-
Более 50 млн. записей → бакеты переполняются, индексация замедляется.
-
Запросы с диапазонами (
<,>,BETWEEN) → хеш-индекс не поддерживает. -
Много дубликатов → это приводит к переполнению страниц и снижению производительности.
Оптимизация хеш-индекса
Если индекс замедляется из-за переполнения бакетов, попробуйте:
✔️ Пересоздать индекс с увеличенным числом бакетов:
⚠️ Важно: С версии 10.0 неактуально, т.к. количество бакетов увеличивается динамически
DROP INDEX my_hash_index; CREATE INDEX my_hash_index ON my_table USING hash(my_column);
✔️ Использовать B-tree, если есть диапазонные запросы.
✔️ Разделить таблицу на партиции, если данные стремительно растут.
Выводы
🔹 Хеш-индексы ускоряют поиск при = и равномерных данных.
🔹 Переполнение бакетов снижает производительность.
🔹 B-tree лучше при диапазонных запросах.
Если данные динамически изменяются и содержат много дубликатов, B-tree может оказаться более эффективным. Однако, при правильных условиях хеш-индекс даёт отличную скорость поиска, особенно на больших объёмах данных с точными соответствиями. 🚀
ссылка на оригинал статьи https://habr.com/ru/articles/882106/
Добавить комментарий