Postgres: Почему добавление индекса замедлило запросы регулярных выражений? - PullRequest
0 голосов
/ 08 июня 2019

У меня есть столбец TEXT keyvalues в Postgres:

select * from test5 limit 5;

 id |                      keyvalues
----+------------------------------------------------------
  1 | ^ first 1 | second 3
  2 | ^ first 1 | second 2 ^ first 2 | second 3
  3 | ^ first 1 | second 2 | second 3
  4 | ^ first 2 | second 3 ^ first 1 | second 2 | second 2
  5 | ^ first 2 | second 3 ^ first 1 | second 3

Мои запросы должны исключать символ ^ из середины совпадения, поэтому я использую регулярные выражения:

explain analyze select count(*) from test5 where keyvalues ~* '\^ first 1[^\^]+second 0';

                                                              QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=78383.31..78383.32 rows=1 width=8) (actual time=7332.030..7332.030 rows=1 loops=1)
   ->  Gather  (cost=78383.10..78383.30 rows=2 width=8) (actual time=7332.021..7337.138 rows=3 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Partial Aggregate  (cost=77383.10..77383.10 rows=1 width=8) (actual time=7328.155..7328.156 rows=1 loops=3)
               ->  Parallel Seq Scan on test5  (cost=0.00..77382.50 rows=238 width=0) (actual time=7328.146..7328.146 rows=0 loops=3)
                     Filter: (keyvalues ~* '\^ first 1[^\^]+second 0'::text)
                     Rows Removed by Filter: 1666668
 Planning Time: 0.068 ms
 Execution Time: 7337.184 ms

Запрос работает (совпадение нулевых строк), но слишком медленно:> 7 секунд.

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

create extension if not exists pg_trgm;
create index on test5 using gin (keyvalues gin_trgm_ops);

explain analyze select count(*) from test5 where keyvalues ~* '\^ first 1[^\^]+second 0';
                                                                   QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=1484.02..1484.03 rows=1 width=8) (actual time=23734.646..23734.646 rows=1 loops=1)
   ->  Bitmap Heap Scan on test5  (cost=1480.00..1484.01 rows=1 width=0) (actual time=23734.641..23734.641 rows=0 loops=1)
         Recheck Cond: (keyvalues ~* '\^ first 1[^\^]+second 0'::text)
         Rows Removed by Index Recheck: 5000005
         Heap Blocks: exact=47620
         ->  Bitmap Index Scan on test5_keyvalues_idx  (cost=0.00..1480.00 rows=1 width=0) (actual time=1756.158..1756.158 rows=5000005 loops=1)
               Index Cond: (keyvalues ~* '\^ first 1[^\^]+second 0'::text)
 Planning Time: 0.412 ms
 Execution Time: 23734.722 ms

Запрос с индексом триграммы в 3 раза медленнее !Он по-прежнему возвращает правильный результат (ноль строк).Я ожидал, что индекс триграммы сразу выяснит, что нигде нет строки second 0, и будет очень быстрым.

(Мотивация: я хочу избежать нормализации keyvalues в другой таблице , поэтому я хочу закодировать соответствующую логику в одном поле TEXT, используя вместо этого индексирование текста и регулярные выражения. Логика работает, но слишком медленная, , как и JSONB .)

Ответы [ 2 ]

2 голосов
/ 11 июня 2019

Как вы видели, это не будет хорошо работать с триграммами. Триграммы не совпадают по границе пространства, поэтому, если все ваши данные содержат одинаковые слова, индекс будет соответствовать каждой строке.

Это может прояснить ситуацию:

with data as (select * from (values ('^ first 1 | second 3'), 
                                    ('^ first 1 | second 2 ^ first 2 | second 3'), 
                                    ('^ first 1 | second 2 | second 3'), 
                                    ('^ first 2 | second 3 ^ first 1 | second 2 | second 2'), 
                                    ('^ first 2 | second 3 ^ first 1 | second 3')
                             ) v(keyvalues)
)
select keyvalues, show_trgm(keyvalues) from data;
                      keyvalues                       |                                               show_trgm
------------------------------------------------------+-------------------------------------------------------------------------------------------------------
 ^ first 1 | second 3                                 | {"  1","  3","  f","  s"," 1 "," 3 "," fi"," se",con,eco,fir,irs,"nd ",ond,rst,sec,"st "}
 ^ first 1 | second 2 ^ first 2 | second 3            | {"  1","  2","  3","  f","  s"," 1 "," 2 "," 3 "," fi"," se",con,eco,fir,irs,"nd ",ond,rst,sec,"st "}
 ^ first 1 | second 2 | second 3                      | {"  1","  2","  3","  f","  s"," 1 "," 2 "," 3 "," fi"," se",con,eco,fir,irs,"nd ",ond,rst,sec,"st "}
 ^ first 2 | second 3 ^ first 1 | second 2 | second 2 | {"  1","  2","  3","  f","  s"," 1 "," 2 "," 3 "," fi"," se",con,eco,fir,irs,"nd ",ond,rst,sec,"st "}
 ^ first 2 | second 3 ^ first 1 | second 3            | {"  1","  2","  3","  f","  s"," 1 "," 2 "," 3 "," fi"," se",con,eco,fir,irs,"nd ",ond,rst,sec,"st "}

Не могли бы вы использовать частичный индекс для исключения строк с ^ в середине?

1 голос
/ 22 июня 2019

Согласно ОП, правильный ответ был дан здесь на DBA.SE пользователем @jjanes:

Я ожидал, что индекс триграммы сразу выяснит, что нигде нет строки second 0

'second' и '0' - это отдельные слова, поэтому он не может обнаружить их совместное отсутствие как таковое. Кажется, что он может обнаружить отсутствие '0', но этот комментарий из "contrib / pg_trgm / trgm_regexp.c" кажется уместным:

     * Note: Using again the example "foo bar", we will not consider the
     * trigram "  b", though this trigram would be found by the trigram
     * extraction code.  Since we will find " ba", it doesn't seem worth
     * trying to hack the algorithm to generate the additional trigram.

Поскольку 0 является последним символом в строке шаблона, триграмма в форме "0a" также не будет, поэтому она просто упускает эту возможность.

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

...