Pull to refresh

Хеш-индексы в PostgreSQL: быстрый поиск или скрытые проблемы?

Level of difficultyEasy
Reading time3 min
Views5.3K

Индексы — важнейший инструмент оптимизации запросов в базах данных. В 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) → хеш-индекс не поддерживает.

  • Много дубликатов → это приводит к переполнению страниц и снижению производительности.

Оптимизация хеш-индекса

Если индекс замедляется из-за переполнения бакетов, попробуйте:

✔️ Перестроить индекс заново, освобождая неиспользуемое пространство и улучшая производительность запросов REINDEX INDEX my_index;

✔️ Использовать B-tree, если есть диапазонные запросы.

✔️ Разделить таблицу на партиции, если данные стремительно растут.

Выводы

🔹 Хеш-индексы ускоряют поиск при = и равномерных данных.

🔹 Переполнение бакетов снижает производительность.

🔹 B-tree лучше при диапазонных запросах.

Если данные динамически изменяются и содержат много дубликатов, B-tree может оказаться более эффективным. Однако, при правильных условиях хеш-индекс даёт отличную скорость поиска, особенно на больших объёмах данных с точными соответствиями. 🚀

Tags:
Hubs:
Total votes 14: ↑13 and ↓1+15
Comments9

Articles