PostgreSQL Производительность запроса в индексированном текстовом поле - PullRequest
1 голос
/ 09 марта 2020

Привет, коллеги-инженеры,

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

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

Таблица по сути очень проста и содержит ~ 10000 записей, и выглядит примерно так:

CREATE TABLE phrase
(
    phrase_id bigint NOT NULL,
    phrase text COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT phrase_pkey PRIMARY KEY (phrase_id),
    CONSTRAINT phrase_phrase_key UNIQUE (phrase)
)

После удаления / Создавая индексы, я запустил VACUUM ANALYZE для таблицы перед выполнением каких-либо объяснений / запросов.

Выполнение следующего запроса без индекса занимает около 82 мс, грубое среднее за несколько попыток. 1012 *

EXPLAIN ANALYSE SELECT phrase FROM phrase WHERE upper(phrase) = ANY('{*PROTEIN SHAKE,*APPLE PIE}');
"Seq Scan on phrase  (cost=0.00..295.72 rows=100 width=18) (actual time=0.049..5.730 rows=2 loops=1)"
"  Filter: (upper(phrase) = ANY ('{"*PROTEIN SHAKE","*APPLE PIE"}'::text[]))"
"  Rows Removed by Filter: 10046"
"Planning Time: 0.135 ms"
"Execution Time: 5.745 ms"

Там может быть несколько фраз, чтобы отступить, поэтому я смотрел на ILIKE следующим образом:

EXPLAIN ANALYSE SELECT phrase FROM phrase WHERE upper(phrase) ~~* ANY('{*PROTEIN SHAKE,*APPLE PIE}');;
"Seq Scan on phrase  (cost=0.00..295.72 rows=2 width=18) (actual time=0.113..19.492 rows=2 loops=1)"
"  Filter: (upper(phrase) ~~* ANY ('{"*PROTEIN SHAKE","*APPLE PIE"}'::text[]))"
"  Rows Removed by Filter: 10046"
"Planning Time: 0.081 ms"
"Execution Time: 19.510 ms"

После создания индекса btree, подобного этому:

CREATE INDEX phrase_phrase_idx ON phrase USING BTREE (upper(phrase) text_pattern_ops ASC NULLS LAST)

Запрос на равенство дает следующее объяснение; выполнение этого запроса занимает около 85 мс.

EXPLAIN ANALYSE SELECT phrase FROM phrase WHERE upper(phrase) = ANY('{*PROTEIN SHAKE,*APPLE PIE}');
"Index Only Scan using phrase_phrase_btree_idx on phrase  (cost=0.29..8.61 rows=2 width=18) (actual time=0.031..0.034 rows=2 loops=1)"
"  Index Cond: ((upper(phrase)) = ANY ('{"*PROTEIN SHAKE","*APPLE PIE"}'::text[]))"
"  Heap Fetches: 0"
"Planning Time: 0.100 ms"
"Execution Time: 0.112 ms"

Запрос ILIKE дает следующее объяснение; Выполнение этого запроса занимает около 95 мс.

EXPLAIN ANALYSE SELECT phrase FROM phrase WHERE upper(phrase) ~~* ANY('{*PROTEIN SHAKE,*APPLE PIE}');
"Seq Scan on phrase  (cost=0.00..295.72 rows=2 width=18) (actual time=0.116..19.043 rows=2 loops=1)"
"  Filter: (upper(phrase) ~~* ANY ('{"*PROTEIN SHAKE","*APPLE PIE"}'::text[]))"
"  Rows Removed by Filter: 10046"
"Planning Time: 0.247 ms"
"Execution Time: 19.060 ms"

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

CREATE INDEX phrase_phrase_idx ON phrase USING BTREE (upper(phrase) text_pattern_ops ASC NULLS LAST) INCLUDE(phrase_id, phrase);

Запуск Следующий запрос (не объяснение) занял около 85 мс.

EXPLAIN ANALYSE SELECT phrase FROM phrase WHERE upper(phrase) = ANY('{*PROTEIN SHAKE,*APPLE PIE}');
"Seq Scan on phrase  (cost=0.00..295.72 rows=100 width=18) (actual time=0.046..5.734 rows=2 loops=1)"
"  Filter: (upper(phrase) = ANY ('{"*)"","*APPLE PIE"}'::text[]))"
"  Rows Removed by Filter: 10046"
"Planning Time: 0.139 ms"
"Execution Time: 5.748 ms"

, и этот запрос ILIKE занял около 95 мс.

EXPLAIN ANALYSE SELECT phrase FROM phrase WHERE upper(phrase) ~~* ANY('{*PROTEIN SHAKE,*APPLE PIE}');
"Seq Scan on phrase  (cost=0.00..295.72 rows=2 width=18) (actual time=0.114..19.340 rows=2 loops=1)"
"  Filter: (upper(phrase) ~~* ANY ('{"*PROTEIN SHAKE","*APPLE PIE"}'::text[]))"
"  Rows Removed by Filter: 10046"
"Planning Time: 0.094 ms"
"Execution Time: 19.357 ms"

Я думал, что попробую индекс GIN:

CREATE INDEX phrase_phrase_gin_idx ON phrase USING GIN (upper(phrase) gin_trgm_ops)

Запрос приравнивания занял ~ 85 мс.

EXPLAIN ANALYSE SELECT phrase FROM phrase WHERE upper(phrase) = ANY('{*PROTEIN SHAKE,*APPLE PIE}');
"Seq Scan on phrase  (cost=0.00..295.72 rows=2 width=18) (actual time=0.056..5.764 rows=2 loops=1)"
"  Filter: (upper(phrase) = ANY ('{"*PROTEIN SHAKE","*APPLE PIE"}'::text[]))"
"  Rows Removed by Filter: 10046"
"Planning Time: 0.059 ms"
"Execution Time: 5.775 ms"

Запрос ILIKE также занял около 85 мс.

EXPLAIN ANALYSE SELECT phrase FROM phrase WHERE upper(phrase) ~~* ANY('{*PROTEIN SHAKE,*APPLE PIE}');
"Bitmap Heap Scan on phrase  (cost=148.02..155.34 rows=2 width=18) (actual time=0.264..0.275 rows=2 loops=1)"
"  Recheck Cond: (upper(phrase) ~~* ANY ('{"*PROTEIN SHAKE","*APPLE PIE"}'::text[]))"
"  Rows Removed by Index Recheck: 2"
"  Heap Blocks: exact=3"
"  ->  Bitmap Index Scan on phrase_phrase_gin_idx  (cost=0.00..148.02 rows=2 width=0) (actual time=0.247..0.247 rows=4 loops=1)"
"        Index Cond: (upper(phrase) ~~* ANY ('{"*PROTEIN SHAKE","*APPLE PIE"}'::text[]))"
"Planning Time: 0.281 ms"
"Execution Time: 0.314 ms"

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

Пожалуйста, кто-нибудь будет достаточно любезен, чтобы указать, где / почему я являюсь пустышкой.

Я бы хотел быть в состоянии существенно изменить этот запрос, но я что-то упустил.

С наилучшими пожеланиями, Пол.

1 Ответ

0 голосов
/ 09 марта 2020

Хотя индексы триграмм являются мощным инструментом, они медленно обновляются и могут стать довольно большими, поэтому, если вы можете делать с индексом B-дерева, используйте его всеми средствами.

У вас есть проверить, дает ли индекс триграммы преимущества в производительности для более длинных списков IN в вашем запросе, но я сомневаюсь, что это так.

Я бы придерживался оригинальных

WHERE upper(phrase) = ANY('{*PROTEIN SHAKE,*APPLE PIE}')

и B Индекс Использование ILIKE ANY вместо = ANY не дает никаких преимуществ, независимо от того, какой индекс вы используете.

В какой-то момент PostgreSQL переключится на последовательное сканирование, если список станет очень длинным, но, возможно, тогда правильно предположить, что часть таблицы, которая должна быть отсканирована, достаточно велика, чтобы сканирование индекса больше не выполнялось быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...