Индекс не используется из-за преобразования типов? - PullRequest
3 голосов
/ 21 июля 2009

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

Может ли неявное преобразование типов остановить использование индекса? А как насчет других причин? Стоимость полного сканирования таблицы примерно на 1000 больше, чем должен быть поиск по индексу.

EDIT:

Оператор SQL:

select unique_key 
from src_table 
where natural_key1 = :1 
and natural_key2 = :2 
and natural_key3 = :3;
  • Количество элементов Natural_key1 высокое, но есть преобразование типов.
  • Другие части естественного ключа имеют низкую мощность и индексы растровых изображений не включены.
  • Размер таблицы составляет около 1 000 000 записей.

Java-код (не легко изменяемый):

ps.setLong(1, oid);

Это противоречит типу столбца: varchar2

Ответы [ 4 ]

11 голосов
/ 21 июля 2009

и неявное преобразование могут препятствовать использованию индекса оптимизатором. Рассмотрим:

SQL> CREATE TABLE a (ID VARCHAR2(10) PRIMARY KEY);

Table created

SQL> insert into a select rownum from dual connect by rownum <= 1e6;

1000000 rows inserted

Это простая таблица, но тип данных не 'правильный', т. Е. Если вы сделаете такой запрос, будет выполнено полное сканирование:

SQL> select * from a where id = 100;

ID
----------
100

Этот запрос фактически эквивалентен:

select * from a where to_number(id) = 100;

Он не может использовать индекс, поскольку мы проиндексировали id, а не to_number(id). Если мы хотим использовать индекс, мы должны быть явными :

select * from a where id = '100';

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

Во время операций SELECT FROM Oracle преобразует данные из столбца в тип целевой переменной.

Это означает, что когда неявное преобразование происходит во время предложения "WHERE column=variable", Oracle преобразует тип данных столбца, а НЕ переменной, поэтому предотвращает использование индекса. Вот почему вы всегда должны использовать правильный тип данных или явно преобразовывать переменную.

Из документа Oracle:

Oracle рекомендует указывать явные преобразования, а не полагаться на неявные или автоматические преобразования по следующим причинам:

  • Операторы SQL легче понять, когда вы используете явные функции преобразования типов данных.
  • Неявное преобразование типов данных может оказать негативное влияние на производительность , особенно если тип данных значения столбца преобразуется в постоянный, а не наоборот.
  • Неявное преобразование зависит от контекста, в котором оно происходит, и может работать не во всех случаях одинаково. Например, неявное преобразование из значения datetime в значение VARCHAR2 может вернуть неожиданный год в зависимости от значения параметра NLS_DATE_FORMAT.
  • Алгоритмы неявного преобразования могут изменяться в разных версиях программного обеспечения и среди продуктов Oracle. Поведение явных преобразований более предсказуемо.
8 голосов
/ 21 июля 2009

Сделайте ваше условие sargable , то есть сравните само поле с постоянным условием.

Это плохо:

SELECT  *
FROM    mytable
WHERE   TRUNC(date) = TO_DATE('2009.07.21')

, так как он не может использовать индекс. Oracle не может повернуть функцию TRUNC(), чтобы получить границы диапазона.

Это хорошо:

SELECT  *
FROM    mytable
WHERE   date >= TO_DATE('2009.07.21')
        AND date < TO_DATE('2009.07.22')

Чтобы избавиться от неявного преобразования, используйте явное преобразование:

Это плохо:

SELECT  *
FROM    mytable
WHERE   guid = '794AB5396AE5473DA75A9BF8C4AA1F74'

-- This uses implicit conversion. In fact this is RAWTOHEX(guid) = '794AB5396AE5473DA75A9BF8C4AA1F74'

Это хорошо:

SELECT  *
FROM    mytable
WHERE   guid = HEXTORAW('794AB5396AE5473DA75A9BF8C4AA1F74')

Обновление:

Этот запрос:

SELECT  unique_key
FROM    src_table
WHERE   natural_key1 = :1
        AND natural_key2 = :2
        AND natural_key3 = :3

сильно зависит от типа ваших полей.

Явно приведите ваши переменные к типу поля, как из строки.

1 голос
/ 21 июля 2009

Что произойдет с вашим запросом, если вы запустите его с явным преобразованием вокруг аргумента (например, to_char (: 1) или to_number (: 1) в зависимости от ситуации)? Если это сделает ваш запрос быстрым, у вас есть ответ.

Однако, если ваш запрос все еще работает медленно с явным преобразованием, может быть другая проблема. Вы не упоминаете, какую версию Oracle вы используете, если в столбце высокой мощности (natural_key1) есть значения с очень искаженным распределением, вы можете использовать план запроса, сгенерированный при первом запуске запроса, который использовал неблагоприятное значение для: 1.

Например, если в вашей таблице из 1 миллиона строк было 400 000 строк с natural_key1 = 1234, а оставшиеся 600 000 были уникальными (или почти), оптимизатор не выбрал бы индекс, если ваш запрос ограничен natural_key1 = 1234. вы используете переменные связывания, если вы выполняете запрос впервые, оптимизатор выберет этот план для всех последующих запусков.

Один из способов проверить эту теорию состоит в том, чтобы запустить эту команду перед выполнением вашего теста:

alter system flush shared_pool;

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

Если это так, вы не хотите использовать эту команду alter system в производственной среде - вы, скорее всего, ухудшите производительность вашей системы, если будете регулярно ее запускать, но вы можете обойти ее, используя динамический sql вместо переменных связывания или, если возможно заранее определить, что: 1 является неселективным, используйте несколько иной запрос для неселективных случаев (например, переупорядочение условий в предложении WHERE, что вызовет оптимизатор) использовать другой план).

Наконец, вы можете попробовать добавить подсказку индекса к вашему запросу, например ::

  SELECT /*+ INDEX(src_table,<name of index for natural_key1>) */
         unique_key
    FROM src_table
   WHERE natural_key1 = :1
     AND natural_key2 = :2
     AND natural_key3 = :3;

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

1 голос
/ 21 июля 2009

Вы можете использовать индекс на основе функций.

Ваш запрос:

select
    unique_key 
from
    src_table
where
    natural_key1 = :1

В вашем случае индекс не используется, потому что natural_key1 является varchar2, а :1 является числом Oracle преобразует ваш запрос в:

select
    unique_key 
from
    src_table
where
    to_number(natural_key1) = :1

Итак ... добавить индекс для to_number(natural_key1):

create index ix_src_table_fnk1 on src_table(to_number(natural_key1));

Ваш запрос теперь будет использовать индекс ix_src_table_fnk1.

Конечно, лучше сначала заставить ваших программистов на Java делать это правильно.

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