Oracle: Полный текстовый поиск с условием
-
28-10-2019 - |
Вопрос
Я создал текстовый индекс Oracle, как следующее:
create index my_idx on my_table (text) indextype is ctxsys.context;
И тогда я могу сделать следующее:
select * from my_table where contains(text, '%blah%') > 0;
Но скажем, у нас есть еще одна колонка в этой таблице, скажем, group_id
, и я хотел вместо этого сделать следующий запрос:
select * from my_table where contains(text, '%blah%') > 0 and group_id = 43;
При вышеуказанном индексе Oracle придется искать все элементы, которые содержат 'blah'
, а затем проверьте все их group_id
с
В идеале я бы предпочел искать только элементы с group_id = 43
, так что я бы хотел такой индекс, как это:
create index my_idx on my_table (group_id, text) indextype is ctxsys.context;
Вроде как обычный индекс, поэтому для каждого можно сделать отдельный текстовый поиск group_id
.
Есть ли способ сделать что -то подобное в Oracle (я использую 10G, если это важно)?
Изменить (разъяснение)
Рассмотрим таблицу с миллионом рядов и следующие два столбца среди других, A
а также B
, оба числовые. Допустим, есть 500 различных значений A
и 2000 различных значений B
, и каждый ряд уникален.
Теперь давайте рассмотрим select ... where A = x and B = y
Индекс на A
а также B
отдельно, насколько я могу судить B
, который вернет 500 различных строк, а затем сделает соединение/сканирование на этих рядах. В любом случае, по крайней мере 500 строк должны быть рассмотрены (кроме того, что база данных повезло, и находить необходимую строку рано.
В то время как индекс на (A,B)
гораздо более эффективен, он находит одну строку в одном поиске индекса.
Поместить отдельные индексы на group_id
И текст, который я чувствую, оставляет только генератор запросов с двумя вариантами.
(1) Используйте group_id
Индекс и сканируйте все полученные ряды для текста.
(2) Используйте текстовый индекс и сканируйте все полученные ряды для group_id
.
(3) Используйте оба индекса и сделайте соединение.
В то время как я хочу:
(4) Используйте (group_id, "text")
индекс для поиска текстового индекса под конкретным group_id
и сканируйте этот текстовый индекс для конкретной строки/рядов, которые мне нужны. Не требуется сканирование, проверка или соединение, так же, как при использовании индекса на (A,B)
.
Решение
Oracle Text
1 - Вы можете повысить производительность, создав индекс контекста с СОРТИРОВАТЬ ПО:
create index my_idx on my_table(text) indextype is ctxsys.context filter by group_id;
В моих тестах filter by
Определенно улучшил производительность, но было все еще немного быстрее использовать индекс Btree на Group_id.
2-Индексы CTXCAT используют «суб-индексы» и, похоже, работают аналогично многоцелевым индексу. Кажется, это вариант (4) Вы ищете:
begin
ctx_ddl.create_index_set('my_table_index_set');
ctx_ddl.add_index('my_table_index_set', 'group_id');
end;
/
create index my_idx2 on my_table(text) indextype is ctxsys.ctxcat
parameters('index set my_table_index_set');
select * from my_table where catsearch(text, 'blah', 'group_id = 43') > 0
Это, вероятно, самый быстрый подход. Использование вышеупомянутого запроса против 120 МБ случайного текста, аналогичного вашему сценарию A и B, требуется только 18 последовательных. Но с другой стороны, создание индекса CTXCAT занял почти 11 минут и использовало 1,8 ГБ пространства.
(ПРИМЕЧАНИЕ: Oracle Text, кажется, работает правильно здесь, но я не знаком с текстом, и я не могу Gaurentee, это не неуместное использование этих индексов, как сказал @nulluserexception.)
Многоколонные индексы против индексных соединений
Для ситуации, которую вы описываете в своем редактировании, обычно Не было бы существенной разницы между использованием индекса на (a, b) и соединением отдельных индексов на A и B. Я создал некоторые тесты с данными, аналогичными тем, которые вы описали Для многоцелевого индекса.
Причина этого в том, что Oracle извлекает данные в блоках. Блок обычно составляет 8K, а индексный блок уже отсортирован, поэтому вы, вероятно, можете соответствовать значениям от 500 до 2000 в нескольких блоках. Если вы беспокоитесь о производительности, обычно IO читать и писать блоки - единственное, что имеет значение. Независимо от того, должен ли Oracle объединиться несколько тысяч рядов - это несущественное количество времени процессора.
Однако это не относится к индексам текста Oracle. Вы можете присоединиться к индексу контекста с индексом Btree («растровый карта и»?), Но производительность плохая.
Другие советы
Я бы поместил индекс на group_id
И посмотрите, достаточно ли это хорошо. Вы не говорите, сколько строк мы говорим или о каком производительности вам нужно.
Помните, что порядок, в котором обрабатываются предикаты, не обязательно является порядок, в котором вы написали их в запросе. Не пытайтесь перехитрить оптимизатора, если у вас нет реальной причины.
Укороченная версия: Там нет необходимости делать это. Оптимизатор запроса достаточно умный, чтобы решить, как лучше всего выбрать ваши данные. Просто создайте индекс Btree на group_id
, т.е.:
CREATE INDEX my_group_idx ON my_table (group_id);
Длинная версия: Я создал сценарий (testperf.sql
) это вставляет 136 рядов фиктивных данных.
DESC my_table;
Name Null Type
-------- -------- ---------
ID NOT NULL NUMBER(4)
GROUP_ID NUMBER(4)
TEXT CLOB
Есть индекс Btree на group_id
. Анкет Чтобы убедиться, что индекс будет фактически использоваться, запустите это как пользователь DBA:
EXEC DBMS_STATS.GATHER_TABLE_STATS('<YOUR USER HERE>', 'MY_TABLE', cascade=>TRUE);
Вот сколько рядов каждый group_id
есть и соответствующий процент:
GROUP_ID COUNT PCT
---------------------- ---------------------- ----------------------
1 1 1
2 2 1
3 4 3
4 8 6
5 16 12
6 32 24
7 64 47
8 9 7
Обратите внимание, что оптимизатор запросов будет использовать индекс только в том случае, если он думает, что это хорошая идея, то есть вы получаете до определенного процента рядов. Итак, если вы попросите его о плане запроса:
SELECT * FROM my_table WHERE group_id = 1;
SELECT * FROM my_table WHERE group_id = 7;
Вы увидите, что для первого запроса он будет использовать индекс, тогда как для второго запроса он выполнит полное сканирование таблицы, так как есть слишком много строк, чтобы индекс был эффективным, когда group_id = 7
.
Теперь рассмотрим другое состояние - WHERE group_id = Y AND text LIKE '%blah%'
(Поскольку я не очень знаком с ctxsys.context
).
SELECT * FROM my_table WHERE group_id = 1 AND text LIKE '%ipsum%';
Глядя на план запроса, вы увидите, что он будут Используйте индекс на group_id
. Анкет Обратите внимание, что порядок ваших условий не важен:
SELECT * FROM my_table WHERE text LIKE '%ipsum%' AND group_id = 1;
Генерирует тот же план запроса. И если вы попытаетесь запустить один и тот же запрос на group_id = 7
, вы увидите, что он возвращается к полному сканированию стола:
SELECT * FROM my_table WHERE group_id = 7 AND text LIKE '%ipsum%';
Обратите внимание, что статистика собирается автоматически Oracle каждый день (она планируется работать каждую ночь и по выходным), чтобы постоянно повышать эффективность оптимизатора запросов. Короче говоря, Oracle делает все возможное, чтобы оптимизировать оптимизатор, поэтому вам не нужно.
У меня нет экземпляра Oracle под рукой, и я не использовал полнотекстовую индексацию в Oracle, но у меня, как правило, была хорошая производительность с встроенные взгляды, что может быть альтернативой такому индексу, который вы имели в виду. Является ли следующий синтаксис законным, когда содержит() вовлечен?
Этот встроенный вид дает вам значения PK строк в группе 43:
(
select T.pkcol
from T
where group = 43
)
Если у группы есть нормальный индекс и не имеет низкой кардинальности, извлечение этого набора должен быть быстрым. Тогда вы снова присоединитесь к этому набору с T:
select * from T
inner join
(
select T.pkcol
from T
where group = 43
) as MyGroup
on T.pkcol = MyGroup.pkcol
where contains(text, '%blah%') > 0
Надеемся, что оптимизатор сможет использовать индекс PK для оптимизации соединения, а затем применить содержит ПРЕДУПРЕЖДЕНИЕ только для групп 43 рядов.