Планы выполнения Oracle при использовании оператора LIKE с функцией DETERMINISTIC - PullRequest
1 голос
/ 17 марта 2011

Теперь у меня действительно сложная задача с планами выполнения Oracle, использующими хаос, когда я использую функцию DETERMINISTIC справа от оператора LIKE. Это моя ситуация:

Ситуация

Я подумал, что было бы разумно выполнить такой запрос (упрощенно):

SELECT [...]
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter(?)

И я бы связал ? с чем-то вроде 'Eder%'. Теперь customers и addresses - очень большие таблицы. Вот почему важно использовать индексы. Конечно, есть регулярный индекс на addresses.cust_id. Но я также создал индекс на основе функций для special_char_filter(customers.surname), который прекрасно работает.

Беда

Проблема в том, что приведенный выше запрос с предложением like создает планы выполнения с FULL TABLE SCANS в addresses. Похоже, что-то в этом запросе не позволяет Oracle использовать индексы на addresses.cust_id.

Обходной путь

Я узнал, что решение моей проблемы таково:

SELECT [...]
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like ?

Я удалил функцию (DETERMINISTIC!) Из правой части оператора like и предварительно вычислил переменную bind в Java. Теперь этот запрос сверхбыстрый, без каких-либо полных таблиц сканирования. Это тоже очень быстро (хотя и не эквивалентно):

SELECT [...]
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) = special_char_filter(?)

Путаница

Я не понимаю этого. Что плохого в том, что в правой части оператора like есть детерминированные функции? Я наблюдал это в Oracle 11.2.0.1.0

Ответы [ 3 ]

2 голосов
/ 18 марта 2011

Проблема в том, что Oracle не знает, что "special_char_filter (?)" Вернет. Если он возвращает «%», то будет очень медленно использовать индекс, поскольку все будет соответствовать. Если он возвращает «A%», он, вероятно, также будет медленным, поскольку (при условии равномерного распределения по всем буквам) будет совпадать около 4% строк. Если он возвращает «% FRED%», он не будет возвращать много строк, но использование сканирования диапазона индекса будет работать плохо, поскольку строки могут находиться в начале, середине или конце индекса, поэтому он должен сделать весь индекс.

Если вы знаете, что special_char_filter всегда будет возвращать строку, содержащую как минимум три «сплошных» символа в начале, то вам, вероятно, повезет больше с

SELECT [...] ОТ клиентов Адреса присоединения addr ON addr.cust_id = cust.id ГДЕ special_char_filter (cust.surname), как special_char_filter (?) AND substr (special_char_filter (cust.surname), 1,3) = substr (special_char_filter (?), 1,3)

с ФБР на субстрате (special_char_filter (cust.surname), 1,3)

Хотя, если предварительный расчет результата в Java работает, то придерживайтесь его.

Кроме этого, я бы, наверное, посмотрел на Oracle Text для совпадений.

2 голосов
/ 17 марта 2011

Это может быть вообще ничего в запросе. Оптимизатор на основе затрат может быть просто сбит с толку и думать, что ПОЛНОЕ СКАНИРОВАНИЕ быстрее. Вы пытались использовать подсказку в запросе, заставляя Oracle использовать ваш индекс?

1 голос
/ 19 марта 2011

Сценарий ниже показывает шаги, которые я использовал для сканирования диапазона индекса по индексу ADDRESSES.Прежде чем вы посмотрите на детали, вы можете просто запустить все это.Если вы не получите два сканирования диапазона индекса для последних двух запросов, возможно, это различие в наших версиях, настройках и т. Д. Я использую 10.2.0.1.0.

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

Это странная проблема, и я не понимаю всего, что происходитЗдесь.Например, я не знаю, почему use_nl работает, а индексные подсказки - нет.

(Обратите внимание, что мое время выполнения основано на повторных выполнениях. При первом запуске некоторые запросы могут выполняться медленнее, поскольку данныене кэшируется.)

--create tables
create table customers (id number, surname varchar2(100), other varchar2(100));
create table addresses (cust_id number, other varchar2(100));

--create data and indexes
insert into customers select level, 'ASDF'||level, level from dual connect by level <= 1000000;
insert into addresses select level, level from dual connect by level <= 1000000;
create index customers_id on customers(id);
create index addresses_cust_id on addresses(cust_id);
create index customers_special_char_filter on customers(special_char_filter(surname));

--create function
create or replace function special_char_filter(surname in varchar) return varchar2 deterministic is
begin
    return replace(surname, 'bad value!', null);
end;
/

--gather stats
begin
    dbms_stats.gather_table_stats(ownname => user, tabname => 'CUSTOMERS', cascade => true);
    dbms_stats.gather_table_stats(ownname => user, tabname => 'ADDRESSES', cascade => true);
end;
/

set autotrace on;

--Index range scan on CUSTOMERS_SPECIAL_CHAR_FILTER, but full table scan on ADDRESSES
--(0.2 seconds)
SELECT *
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter('ASDF100000bad value!%');

--This uses the addresses index but it does an index full scan.  Not really what we want.
--I'm not sure why I can't get an index range scan here.
--Various other index hints also failed here.  For example, no_index_ffs won't stop an index full scan.
--(1 second)
SELECT /*+ index(addr addresses_cust_id) */ *
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter('ASDF100000bad value!%');


--Success!  With this hint both indexes are used and it's super-fast.
--(0.02 seconds)
SELECT /*+ use_nl(cust addr) */ *
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter('ASDF100000bad value!%');


--But forcing the index won't always be a good idea, for example when the value starts with '%'.
--(1.2 seconds)
SELECT /*+ use_nl(cust addr) */ *
FROM customers cust
JOIN addresses addr ON addr.cust_id = cust.id
WHERE special_char_filter(cust.surname) like special_char_filter('%ASDF100000bad value!%');
...