Oracle: полнотекстовый поиск с условием - PullRequest
11 голосов
/ 09 сентября 2011

Я создал текстовый индекс 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 s.

В идеале, я бы предпочел искать только элементы с 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).

Ответы [ 4 ]

8 голосов
/ 16 сентября 2011

Oracle Text

1 - Вы можете повысить производительность, создав индекс CONTEXT с помощью FILTER BY :

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.)

Многостолбцовые индексы и объединения индексов

Для ситуации, которую вы описываете в своемedit, обычно не будет существенной разницы между использованием индекса на (A, B) и объединением отдельных индексов на A и B. Я построил некоторые тесты с данными, аналогичными описанным вами, и соединением индексатребуется только 7 согласованных получений против 2 согласованных получений для индекса из нескольких столбцов.

Причина этого в том, что Oracle извлекает данные в блоках.Блок обычно имеет размер 8 КБ, а индексный блок уже отсортирован, так что вы, вероятно, можете разместить значения от 500 до 2000 в несколько блоков.Если вы беспокоитесь о производительности, обычно важен только ввод-вывод для чтения и записи блоков.Нужно ли Oracle объединять несколько тысяч строк - это несущественное количество процессорного времени.

Однако это не относится к индексам Oracle Text.Вы можете присоединить индекс CONTEXT к индексу btree («растровое изображение и»?), Но производительность низкая.

1 голос
/ 10 сентября 2011

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

Помните, порядок, в котором обрабатываются предикаты, не обязательно является порядком, в котором вы записали их в запросе. Не пытайтесь перехитрить оптимизатор, если у вас нет настоящей причины.

1 голос
/ 11 сентября 2011

Короткая версия: Нет необходимости делать это.Оптимизатор запросов достаточно умен, чтобы решить, каков наилучший способ выбора ваших данных.Просто создайте индекс 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 делает все возможное для оптимизации оптимизатора, так что вам не нужно это делать.

0 голосов
/ 10 сентября 2011

У меня нет под рукой экземпляра 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.

...