Варіації продуктивності запитів PostgreSQL LIKE


112

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

Я розумію, що LIKEзапити дуже ресурсомісткі, але я просто не розумію, чому може бути така велика різниця у часі відповідей. Я створив індекс btree на owner1полі, але не думаю, що це допомагає в 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

З подібними результатами.
Кількість рядків таблиці: близько 95 000.

Відповіді:


281

FTS не підтримує 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і для регулярних виразів, майте на увазі, що візерунок без триграмів, що витягуються, перероджується до сканування з повним індексом.

Це означає, що сканування індексу / растрових зображень все ще працює (плани запитів для підготовленої операції не зламаються), вона просто не дасть вам кращої продуктивності. Зазвичай великі втрати не мають, оскільки рядки з 1 або 2 літерами навряд чи є вибірковими (більше ніж кілька відсотків збігів основної таблиці), а підтримка індексу не покращить продуктивність для початку, оскільки повне сканування таблиці відбувається швидше.


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

Для лише лівозакріплених шаблонів (без провідних підстановок) ви отримуєте оптимум із відповідним класом операторів для btree index: 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:


Не маючи провідних підстановок на таблиці з 500K рядків, індекс джину з gin_trgm_ops виявляється в 10 разів швидшим, ніж btree
Ніколас

@nicolas: Порівняння залежить від багатьох змінних. Довжина ключа, розподіл даних, довжина шаблону, можливе сканування лише індексом ... І найголовніше: версія Postgres. Індекси GIN суттєво покращені у пг 9,4 та 9,5. Нова версія pg_trgm (вийде з pg 9.6) принесе більше вдосконалень.
Ервін Брандстеттер

1
Якщо я правильно зрозумів документи, pg_trgmвам потрібна рядок запиту довжиною принаймні 3 символи, наприклад fo%, не вдарив би до індексу, а зробив сканування замість цього. Щось зауважити.
Tuukka Mustonen

1
@TuukkaMustonen: Добре. Що ж, (растрові) сканування покажчиків все ще працюють , вони просто не дадуть вам кращої продуктивності. Я додав трохи уточнень вище.
Erwin Brandstetter

7

Можливо, швидкі - це прикріплені моделі з урахуванням регістру, які можуть використовувати індекси. тобто немає підказки на початку рядка відповідності, тому виконавець може використовувати сканування діапазону індексу. ( відповідний коментар у документах є тут ) Нижній або ike також втратить вашу здатність використовувати індекс, якщо ви спеціально не створите індекс для цієї мети (див. функціональні індекси ).

Якщо ви хочете шукати рядок посередині поля, вам слід ознайомитись з повнотекстовими або триграмовими індексами . Перший з них знаходиться в ядрі Postgres, другий доступний в модулях contrib.


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

4

Ви можете встановити Wildspeed , інший тип індексу в PostgreSQL. Wildspeed працює з% word% wildcards, немає проблем. Недоліком є ​​розмір індексу, це може бути великим, дуже великим.


3

Будь ласка, виконайте нижче згаданий запит для поліпшення продуктивності запиту 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, верхній, нижній і т.д. Вона виконає повне сканування таблиці, що проходить через кожен ряд, і тому буде повільним.

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

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.