Оптимизированный поиск с PostgreSQL
Ваш поиск привязан в начале и . Нечеткая логика поиска не требуется. Это не типичный вариант использования для полнотекстового поиска.
Если он становится более размытым или ваш поиск не привязан в начале, ищите здесь больше:
Аналогичные строки UTF-8 для поля автозаполнения
Подробнее о сопоставлении с образцом в Postgres.
В PostgreSQL вы можете использовать расширенные функции индекса , которые должны сделать запрос очень быстрым . В частности, посмотрите на классы операторов и индексы для выражений .
1) text_pattern_ops
Предполагая, что ваш столбец имеет тип text, вы должны использовать специальный индекс для операторов текстового шаблона , например:
CREATE INDEX name_text_pattern_ops_idx
ON tbl (name text_pattern_ops);
SELECT name
FROM tbl
WHERE name ~~ ('Hambu' || '%');
Предполагается, что вы работаете с языком базы данных, отличным от C
- скорее всего, de_DE.UTF-8
в вашем случае. Вы могли бы также настроить базу данных с языковым стандартом 'C'. Я цитирую руководство здесь :
Если вы используете локаль C, вам не нужен xxx_pattern_ops
классы операторов, потому что индекс с классом операторов по умолчанию
может использоваться для запросов на сопоставление с образцом в локали C.
2) Указатель на выражение
Я полагаю, вы также захотите сделать этот поиск без учета регистра . так что давайте сделаем еще один шаг и создадим индекс для выражения:
CREATE INDEX lower_name_text_pattern_ops_idx
ON tbl (lower(name) text_pattern_ops);
SELECT name
FROM tbl
WHERE lower(name) ~~ (lower('Hambu') || '%');
Чтобы использовать индекс, предложение WHERE
должно соответствовать выражению индекса.
3) Оптимизация размера индекса и скорости
Наконец, вы также можете наложить ограничение на число ведущих символов , чтобы минимизировать размер вашего индекса и еще больше ускорить процесс:
CREATE INDEX lower_left_name_text_pattern_ops_idx
ON tbl (lower(left(name,10)) text_pattern_ops);
SELECT name
FROM tbl
WHERE lower(left(name,10)) ~~ (lower('Hambu') || '%');
left()
был представлен Postgres 9.1. Используйте substring(name, 1,10)
в старых версиях.
4) Покрыть все возможные запросы
А как насчет строк длиной более 10 символов?
SELECT name
FROM tbl
WHERE lower(left(name,10)) ~ (lower(left('Hambu678910',10)) || '%');
AND lower(name) ~~ (lower('Hambu678910') || '%');
Это выглядит избыточно, но вам нужно прописать это таким образом, чтобы реально использовать индекс. Поиск по индексу сузит его до нескольких записей, дополнительное предложение фильтрует остальное. Эксперимент, чтобы найти сладкое место. Зависит от распределения данных и типичных случаев использования. 10 символов кажутся хорошей отправной точкой. Для более чем 10 символов left()
фактически превращается в очень быстрый и простой алгоритм хеширования, который подходит для многих (но не для всех) вариантов использования.
5) Оптимизировать представление диска с помощью CLUSTER
Таким образом, преобладающим шаблоном доступа будет получение группы смежных строк в соответствии с нашим индексом lower_left_name_text_pattern_ops_idx
. И вы в основном читаете и почти никогда не пишете . Это учебник для CLUSTER
. Я цитирую руководство :
Когда таблица кластеризована, она физически переупорядочивается на основе информации индекса.
С такой огромной таблицей, как ваша, это может значительно сократить время отклика, поскольку все извлекаемые строки находятся в одинаковых или соседних блоках на диске.
Первый звонок:
CLUSTER tbl USING lower_left_name_text_pattern_ops_idx;
Информация о том, какой индекс будет использоваться, будет сохранена, и последующие вызовы повторно объединят таблицу:
CLUSTER tbl;
CLUSTER; -- cluster all tables in the db that have previously been clustered.
Если вы не хотите повторять это:
ALTER TABLE tbl SET WITHOUT CLUSTER;
Для таблиц с большей нагрузкой записи смотрите pg_repack
, который может делать то же самое без исключительной блокировки таблицы.
6) Запретить слишком много строк в результате
Требуется минимум 3 или 4 символа для строки поиска. Я добавляю это для полноты, вы, вероятно, делаете это в любом случае.
И LIMIT
количество возвращаемых строк:
SELECT name
FROM tbl
WHERE lower(left(name,10)) ~~ (lower('Hambu') || '%')
LIMIT 501;
Если ваш запрос возвращает более 500 строк, попросите пользователя сузить область поиска.
7) Оптимизировать метод фильтрации (операторы)
Если вам абсолютно необходимо выжать каждую последнюю микросекунду, вы можете использовать операторы семейства text_pattern_ops .Вот так:
SELECT name
FROM tbl
WHERE lower(left(name, 10)) ~>=~ lower('Hambu')
AND lower(left(name, 10)) ~<=~ (lower('Hambu') || chr(2097151));
Вы получаете очень мало с этим последним трюком.Обычно стандартные операторы - лучший выбор.
Если вы все это сделаете, время поиска будет сокращено до нескольких миллисекунд.