Варианты производительности запросов LIKE в PostgreSQL


114

Я видел довольно большие различия во времени ответа на LIKEзапросы к конкретной таблице в моей базе данных. Иногда я получаю результаты в пределах 200-400 мс (очень приемлемо), но в других случаях для получения результатов может потребоваться до 30 секунд.

Я понимаю, что LIKEзапросы очень ресурсоемкие, но я просто не понимаю, почему может быть такая большая разница во времени ответа. Я создал для owner1поля индекс btree, но не думаю, что это помогает с LIKEзапросами. У кого-нибудь есть идеи?

Пример SQL:

SELECT gid, owner1 FORM parcels
WHERE owner1 ILIKE '%someones name%' LIMIT 10

Я также пробовал:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%') LIMIT 10

И:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('someones name%') LIMIT 10

С похожими результатами.
Количество строк в таблице: около 95000.

Ответы:


284

ФНС не поддерживает LIKE

Ранее принят ответ был неверным. Полнотекстовый поиск с его полнотекстовыми индексами вообще не для LIKEоператора, он имеет свои операторы и не работает для произвольных строк. Он оперирует словами на основе словарей и корней. Это делает поддержку согласования префикса для слов , но не с LIKEоператором:

Индексы триграмм для LIKE

Установите дополнительный модуль, pg_trgmкоторый предоставляет классы операторов для индексов триграмм GIN и GiST для поддержки всех LIKEи ILIKEшаблонов , а не только с левым якорем:

Пример индекса:

CREATE INDEX tbl_col_gin_trgm_idx  ON tbl USING gin  (col gin_trgm_ops);

Или:

CREATE INDEX tbl_col_gist_trgm_idx ON tbl USING gist (col gist_trgm_ops);

Пример запроса:

SELECT * FROM tbl WHERE col LIKE '%foo%';   -- leading wildcard
SELECT * FROM tbl WHERE col ILIKE '%foo%';  -- works case insensitively as well

Триграммы? А как насчет более коротких струн?

Слова с менее чем 3 буквами в индексированных значениях по-прежнему работают. Руководство:

При определении набора триграмм, содержащихся в строке, считается, что каждое слово имеет два префикса и один суффикс.

И поисковые шаблоны с менее чем 3 буквами? Руководство:

При LIKEпоиске как по регулярным выражениям, так и по регулярным выражениям имейте в виду, что шаблон без извлекаемых триграмм будет вырожден в сканирование полного индекса.

Это означает, что сканирование индекса / растрового индекса по-прежнему работает (планы запросов для подготовленного оператора не нарушаются), это просто не даст вам лучшей производительности. Как правило, нет больших потерь, поскольку одно- или двухбуквенные строки вряд ли являются избирательными (более нескольких процентов совпадений с базовой таблицей), а поддержка индекса не улучшит производительность с самого начала, потому что полное сканирование таблицы выполняется быстрее.


text_pattern_ops для сопоставления префикса

Только для шаблонов с левым якорем (без ведущего подстановочного знака) вы получите оптимум с подходящим классом операторов для индекса btree: text_pattern_opsили varchar_pattern_ops. Обе встроенные функции стандартного Postgres, дополнительный модуль не требуется. Аналогичная производительность, но гораздо меньший индекс.

Пример индекса:

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

Пример запроса:

SELECT * FROM tbl WHERE col LIKE 'foo%';  -- no leading wildcard

Или , если вы должны запускать свою базу данных с локалью «C» (фактически без локали), тогда все в любом случае сортируется в соответствии с порядком байтов, и простой индекс btree с классом оператора по умолчанию выполняет свою работу.

Более подробная информация, объяснение, примеры и ссылки в этих связанных ответах на dba.SE:


Без ведущего подстановочного знака в таблице из 500 тыс. Строк индекс gin с gin_trgm_ops оказывается в 10 раз быстрее, чем btree
Николас

@nicolas: Сравнение зависит от многих переменных. Длина ключа, распределение данных, длина шаблона, возможно сканирование только индекса ... И самое главное: версия Postgres. Индексы GIN были существенно улучшены на страницах 9.4 и 9.5. Новая версия pg_trgm (которая будет выпущена с pg 9.6) принесет больше улучшений.
Эрвин Брандштеттер,

1
Если я правильно понял документы, pg_trgmвам понадобится строка запроса длиной не менее 3 символов, например fo%, не попадет в индекс, а вместо этого выполнит сканирование. Кое-что отметить.
Туукка Мустонен

1
@TuukkaMustonen: Хороший момент. Что ж, сканирование (растрового) индекса все еще работает , оно просто не даст вам лучшей производительности. Я добавил некоторые пояснения выше.
Эрвин Брандштеттер

7

Возможно, самые быстрые - это привязанные шаблоны с чувствительностью к регистру, которые могут использовать индексы. т.е. в начале строки соответствия нет подстановочного знака, поэтому исполнитель может использовать сканирование диапазона индексов. ( соответствующий комментарий в документации находится здесь ) Lower и ilike также потеряют вашу способность использовать индекс, если вы специально не создадите индекс для этой цели (см. функциональные индексы ).

Если вы хотите найти строку в середине поля, вам следует изучить полнотекстовый или триграммный индексы . Первый из них находится в ядре Postgres, другой доступен в модулях contrib.


Я не думал о создании индекса для нижнего регистра значения поля. Таким образом, я могу преобразовать текст запроса в нижний регистр на сервере перед запросом.
Джейсон,

4

Вы можете установить Wildspeed , другой тип индекса в PostgreSQL. Wildspeed работает с подстановочными знаками% word%, без проблем. Обратной стороной является размер индекса, он может быть большим, очень большим.


4

Выполните указанный ниже запрос, чтобы повысить производительность запроса LIKE в postgresql. создайте такой индекс для больших таблиц:

CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)

Это работает только в том случае, если шаблон не начинается с подстановочного знака - в этом случае первые два примера запроса начинаются с подстановочного знака.
cbz

1

как бы то ни было, Django ORM имеет тенденцию использовать UPPER(text)для всех LIKEзапросов, чтобы сделать его нечувствительным к регистру,

Добавление индекса UPPER(column::text)значительно ускорило мою систему, в отличие от всего остального.

Что касается ведущего%, да, он не будет использовать индекс. См. Этот блог для отличного объяснения:

https://use-the-index-luke.com/sql/where-clause/searching-for-ranges/like-performance-tuning


1

Недавно у меня была аналогичная проблема с таблицей, содержащей 200000 записей, и мне нужно выполнять повторяющиеся запросы LIKE. В моем случае искомая строка была исправлена. Остальные поля менялись. Благодаря этому я смог переписать:

SELECT owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%');

так как

CREATE INDEX ix_parcels ON parcels(position(lower('someones name') in lower(owner1)));

SELECT owner1 FROM parcels
WHERE position(lower('someones name') in lower(owner1)) > 0;

Я был рад, когда запросы вернулись быстро, и подтвердил, что индекс используется с EXPLAIN ANALYZE:

 Bitmap Heap Scan on parcels  (cost=7.66..25.59 rows=453 width=32) (actual time=0.006..0.006 rows=0 loops=1)
   Recheck Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
   ->  Bitmap Index Scan on ix_parcels  (cost=0.00..7.55 rows=453 width=0) (actual time=0.004..0.004 rows=0 loops=1)
         Index Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
 Planning time: 0.075 ms
 Execution time: 0.025 ms

0

Ваши похожие запросы, вероятно, не могут использовать созданные вами индексы, потому что:

1) ваш критерий LIKE начинается с подстановочного знака.

2) вы использовали функцию с вашим критерием LIKE.


0

Когда вы когда-либо используете предложение в столбце с функциями, например, LIKE, ILIKE, upper, lower и т. Д. Тогда postgres не будет принимать во внимание ваш обычный индекс. Он будет выполнять полное сканирование таблицы, просматривая каждую строку, и поэтому он будет медленным.

Правильный способ - создать новый индекс в соответствии с вашим запросом. Например, если я хочу сопоставить столбец без учета регистра, а мой столбец - varchar. Тогда вы можете сделать это вот так.

create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);

Точно так же, если ваш столбец представляет собой текст, вы делаете что-то вроде этого

create index ix_tblname_col_upper on tblname (UPPER(col) text_pattern_ops);

Точно так же вы можете изменить верхнюю функцию на любую другую функцию, которую хотите.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.