Поиск по индексам выражений - PullRequest
2 голосов
/ 19 сентября 2019

Поиск по индексам выражений

Я строю индекс выражений, который подстрокует исходное поле, чтобы избежать переполнения ограничения в 2172 символа для B-деревьев:

CREATE INDEX record_changes_log_detail_old_value_ix_btree
    ON record_changes_log_detail 
    USING btree ((substring(old_value,1,1024)::text) text_pattern_ops);

Для записи:

- Postgres 11.4 на RDS, 11.5 на macOS в домашних условиях.
- Таблица record_changes_log_detail имеет около 8M в моей тестовой установке.
- Поле old_value имеет тип citext.
- Значения в поле имеют длину от 1 символа до более 5000.Большинство из них короткие.

Этот поиск использует индекс, указанный выше:

select * from record_changes_log_detail 
where substring(old_value,1,1024) = 'Gold Kerrison Neuro';

Этот поиск не использует индекс:

where old_value = 'Gold Kerrison Neuro';

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

Я предполагал, что при построении индекса выражения сокращенно / извлеченный / и т. Д.Значение было сохранено, и планировщик проверит его. Есть ли какой-либо способ намека на планировщика, кроме как пересчет всего выражения? Индекс содержит правильные данные, но планировщик, кажется, пропускает его.

Я добавляю немногоподробностей, основанных на ответе Эрвина Брандштеттера:

У меня есть лотов подобных ситуаций, поэтому я копаю здесь детали.В этом случае из моих ~ 8M строк только 6 имеют значения длиннее 2172 символов, и 99,93% значений составляют 100 символов или менее.

Я надеюсь, что подход будет простым длякого-то другого подобрать.Теневое поле вполне может быть ответом, поскольку необходимость знать точные подробности конструкций индекса представляется мне совершенно неправильным видом видимости.Теневое поле не страдает от этой проблемы, если вы знаете, как его использовать.Я мог бы либо заполнить его LEFT (old_field, 128) или какой-либо другой длиной, либо texthash (old_field), как вы упомянули.Я поэкспериментирую с этим.Мои данные настолько искажены до коротких значений, что кажется, что хеширование приводит к высокой частоте столкновений.

Для чего бы то ни было, команда и я пришли из системы, где текстовые поля беззвучно обрезаются до 1024 символов, когдаиндексируется в B-дереве.Это полностью прозрачно для пользователя, и поиски обращаются к индексу.Яблоки и свечи зажигания я знаю.Дело в том, что я не ожидаю, что Постгрес будет ИИ, но я am иду с неточными приорами.Так что спасибо вам и всем остальным за то, что помогли мне узнать больше о том, как на самом деле работает Postgres.


Последующие действия

На этот вопрос ответили, но яЯ хочу добавить продолжение для архивов.Я учил много из старых ответов, некоторые очень старые.Так что вот немного информации на будущее.Я опробовал четыре решения:

  • B-дерево на части поля citext.
  • B-дерево на хэше поля citext.
  • Hashиндекс поля citext.
  • Три-граммовый индекс GIN поля citext.

Поскольку, похоже, нет никакого способа получить запросы типа LIKE для citext, гдетекст может быть слишком длинным, цель состоит в том, чтобы создать индекс для =.Любая из трех вышеперечисленных будет работать нормально, но они немного отличаются.Вот некоторый установочный код для теста:

DROP INDEX IF EXISTS record_changes_log_detail_old_value_ix_btree;
CREATE INDEX record_changes_log_detail_old_value_ix_btree
    ON record_changes_log_detail
    USING btree ((left(old_value,1024)::citext) citext_pattern_ops);


DROP INDEX IF EXISTS record_changes_log_detail_old_value_hash_ix_btree;
CREATE INDEX record_changes_log_detail_old_value_hash_ix_btree
    ON record_changes_log_detail
    USING btree (hashtext(old_value));

DROP INDEX IF EXISTS record_changes_log_detail_old_value_ix_hash;
CREATE INDEX record_changes_log_detail_old_value_ix_hash
    ON record_changes_log_detail
    USING hash (old_value);    

DROP INDEX IF EXISTS record_changes_log_detail_old_value_ix_tgrm;
CREATE INDEX record_changes_log_detail_old_value_ix_tgrm
    ON record_changes_log_detail 
    USING gin (old_value gin_trgm_ops);

VACUUM ANALYZE;

Каждый из этих индексов работает для поиска записи, но с разным синтаксисом:

-- Uses the LEFT()::citext index
explain analyze
select * from record_changes_log_detail 
where left(old_value,1024)::citext = 'Gold Kerrison Neuro';

-- Uses the HASH index
explain analyze
select * from record_changes_log_detail 
where old_value = 'Gold Kerrison Neuro';

-- Uses the HASHTEXT() index
explain analyze
select * from record_changes_log_detail 
where hashtext(old_value) = hashtext('Gold Kerrison Neuro');

-- Uses the tri-gram() index
explain analyze
select * from record_changes_log_detail 
where old_value::text LIKE '%Gold Kerrison Neuro%';

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

select
'B-tree on LEFT(old_value,1024)::citext' as index_description,
pg_size_pretty(pg_relation_size ('record_changes_log_detail_old_value_ix_btree')) as pretty

union all

select
'B-tree on HASHTEXT(old_value)' as index_description,
pg_size_pretty(pg_relation_size ('record_changes_log_detail_old_value_hash_ix_btree')) as pretty

union all

select
'Hash index on old_value' as index_description,
pg_size_pretty(pg_relation_size ('record_changes_log_detail_old_value_ix_hash')) as pretty

union all

select
'GIN tri-gram index on old_value' as index_description,
pg_size_pretty(pg_relation_size ('record_changes_log_detail_old_value_ix_tgrm')) as pretty;


index_description                       pretty  seconds
B-tree on LEFT(old_value,1024)::citext  238 MB       38
B-tree on HASHTEXT(old_value)           166 MB        7
Hash index on old_value                 362 MB    3,802
GIN tri-gram index on old_value         106 MB       56

Я бы сказал, что эти данные ужасно соответствуют хэш-индексу, поэтому, пожалуйста, не воспринимайте эти результаты как типичные.Тем не менее, время и размер довольно плохие.Очевидный победитель поисков = - это умное предложение Эрвина Брандштеттера B-tree хэш.Приятно!Дополнительный синтаксический сахар, необходимый для поиска, здесь не так плох, как для индекса на основе LEFT.Заглядывая вперед, это выиграет от улучшений B-дерева, обещанных в PG 12.

И еще хорошая новость, индекс триграмма просто потрясающий .Лоренц Альбе предложила попробовать, и я счастлива, что сделала.Мгновенное содержит / как поиски, идеально.Это как раз то, что мне нужно.Здесь я опять сомневаюсь, что размер индекса типичный ... мои данные странные.Для тех, кто использует citext, обратите внимание, что вы должны привести условие поиска к тексту для индекса, который будет использоваться:

select * from record_changes_log_detail 
where old_value::text LIKE '%Gold Kerrison Neuro%';

Для тех, кто не знает, триграммы являются экземпляром n-граммдлиной 3. N-граммы иногда называют q-граммами или k-граммами.Как бы то ни было, это одно и то же.Из всех наивных (не вероятностных или статистических) алгоритмов нечеткого сопоставления текста это, вероятно, лучший.Надежный для разных наборов данных и языков, гибкий, потрясающий.Так что я очень доволен тем, как хорошо он работает в Postgres.

1 Ответ

1 голос
/ 19 сентября 2019

Это так же, как вы читаете из jjanes в другом месте: индекс выражения учитывается только в том случае, если выражение точно соответствует предикату запроса.Планировщик запросов Postgres не является AI.Если бы планирование занимало слишком много времени, это быстро лишило бы цели сделать запросы быстрыми.

Вы можете немного оптимизировать свой индекс, если это утешит.left() проще и быстрее, чем substring():

CREATE INDEX record_changes_log_detail_old_value_ix_btree
ON record_changes_log_detail (left(old_value,1024) text_pattern_ops);

Также существует максимальный размер строки 2704 байта для индексов btree, а не "2172 символовпредел для B-деревьев ".

Наиболее важно, что только для проверок на равенство, как предполагает ваш вопрос, индекс btree для значения хеша с использованием md5(old_value) или hashtext(old_value) будет много более эффективно.Если вы это сделаете, не забудьте защитить от коллизий хешей примерно так:

SELECT *
FROM   record_changes_log_detail 
WHERE  hashtext(old_value) = hashtext('Gold Kerrison Neuro')
<b>AND    old_value = 'Gold Kerrison Neuro'</b>;

Первый предикат дает вам быстрый доступ к индексу.Второе исключает ложные срабатывания.Столкновения должны быть крайне редкими.Но возможно.И вероятность растет с размером таблицы.

Связанный:

Или хеш-индекс , как вы уже рассматривали:

(Здесь вам не нужно беспокоиться о хеш-коллизиях; обрабатывается внутри.)

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