Как вы препятствуете оптимизатору Oracle на основе затрат проводить плохие оптимизации? - PullRequest
3 голосов
/ 11 ноября 2010

Рассмотрим следующий сценарий.У меня есть таблица (stupid_table) в схеме, которую я не могу контролировать.Это третье лицо, запрещено.Не обидчивый.Я могу запросить его, но не добавлять индексы или новые таблицы или изменять дизайн.

Каждый столбец в stupid_table - это VARCHAR2(50 BYTE), есть много столбцов, но мне нужны только два из них: row_type и magic_number.magic_number заполняется строковым представлением целого числа, но only , где row_type установлено на 'DATA', мне нужны только магические числа, которые больше нуля.

SELECT TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND TO_NUMBER(magic_number) > 0;

Это приводит к ошибке Oracle "недопустимое число", поскольку оптимизатор на основе затрат (CBO) выбирает оценку TO_NUMBER перед проверкой row_type, и существует целый ряд строк с другим row_type и другое использование для поля magic_number.

ОК, как насчет того, чтобы сначала отфильтровать строки, а затем выполнить сравнение?

SELECT TO_NUMBER(t.magic_number)
FROM (
    SELECT magic_number
    FROM stupid_table
    WHERE row_type = 'DATA'
) t
AND TO_NUMBER(t.magic_number) > 0;

Теперь CBO, похоже, работаетВыяснилось, что запрос довольно прост и игнорирует хитрость, которую я использовал, что дает план запроса, идентичный исходному.заставить Oracle использовать старый оптимизатор на основе правил.Это работает как сон, но в этом нет необходимости, не говоря уже о том, что он использует функцию Oracle, которая больше не поддерживается.

Есть ли лучший способ сделать это?

Ответы [ 8 ]

4 голосов
/ 11 ноября 2010

Заставьте CASE сделать всю работу за вас

select to_number(magic_number) 
from stupid_table
where row_type = 'DATA'
and case when row_type = 'DATA' then to_number(magic_number) else 0 end > 0

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

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

SELECT --+ no_merge(t)
  TO_NUMBER(t.magic_number)
FROM (
    SELECT magic_number
    FROM mike_temp_stupid_table
    WHERE row_type = 'DATA'
) t
where TO_NUMBER(t.magic_number) > 0;
4 голосов
/ 11 ноября 2010

Я бы решил эту проблему, написав вашу собственную функцию преобразования, которая поглощает исключение, например

CREATE OR REPLACE FUNCTION my_to_number( p_str IN VARCHAR2 )
  RETURN number
IS 
BEGIN
  RETURN to_number( p_str );
EXCEPTION
  WHEN OTHERS THEN
    RETURN null;
END;

, а затем изменил бы запрос

SELECT TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND MY_TO_NUMBER(magic_number) > 0;

За исключением этого, вы, безусловно, могли бы взятьплан запроса, сгенерированный RBO и создающий профиль, который заставляет CBO использовать этот план.Возможно, это немного проще в управлении, чем попытка предоставить полный набор подсказок, которые не позволят CBO когда-либо применять предикат MAGIC_NUMBER перед предикатом ROW_TYPE.

3 голосов
/ 09 мая 2011

Точный способ - использовать подсказку ordered_predicates, чтобы изменить порядок оценки ваших условий WHERE.

Документация: Oracle ORDERED_PREDICATES Подсказка

SELECT /*+ ORDERED_PREDICATES */ TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND TO_NUMBER(magic_number) > 0;

Теперь попробуйте поменять условия, и вы снова получите ошибку. Пожалуйста, рассмотрите другие ответы, потому что я также сомневаюсь, что вызов TO_NUMBER - это ваше лучшее решение.

3 голосов
/ 11 ноября 2010

Можете ли вы просто отказаться от использования TO_NUMBER в целом? Похоже, это улучшит производительность в любом случае. Что-то вроде:

WHERE t.magic_number != '0'

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

2 голосов
/ 12 ноября 2010

Я обычно добавляю rownum, чтобы остановить толкование предикатов. (Подсказки могут сделать это тоже, но их так легко ошибиться, и с этим типом проблемы, если вы ошибаетесь, вы можете не заметить сразу.) Также вам, вероятно, следует добавить комментарий, чтобы кто-то позже не пытался «оптимизируйте» свой код и удалите ненужную логику.

SELECT TO_NUMBER(t.magic_number)
FROM (
    --Bad data, use rownum for type safety
    SELECT magic_number, rownum
    FROM stupid_table
    WHERE row_type = 'DATA'
) t
AND TO_NUMBER(t.magic_number) > 0;
2 голосов
/ 11 ноября 2010

Как насчет создания материализованного представления фрагмента stupid_table, который содержит только тип строки 'DATA'?

1 голос
/ 11 ноября 2010

Оператор with позволяет применять определенный порядок оценки.

WITH
has_numerics_only AS
(
    SELECT magic_number
    FROM stupid_table
    WHERE row_type = 'DATA'
)
SELECT TO_NUMBER(t.magic_number)
FROM has_numerics_only
WHERE TO_NUMBER(t.magic_number) > 0;

Также рассмотрите возможность того, что в одной или нескольких строках «ДАННЫЕ» действительно имеются неверные данные.

0 голосов
/ 11 ноября 2010

Вы можете попробовать:

SELECT TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND REGEXP_LIKE(magic_number, '^\d{1,}$');

Если это по-прежнему не работает, перемещение условия в предложение HAVING может заставить оптимизатор сначала оценить его.

SELECT TO_NUMBER(magic_number)
FROM (
SELECT magic_number
FROM stupid_table
WHERE row_type = 'DATA'
GROUP BY magic_number
HAVING REGEXP_LIKE(magic_number, '^\d{1,}$')) ilv;

Сбойчто материализованное представление или использование курсора PL / SQL может быть единственным путем.

...