Вопрос

У нас есть два стола, вот так:

Event
    id
    type
    ... a bunch of other columns

ProcessedEvent
    event_id
    process

Существуют индексы, определенные для

  • Событие (идентификатор) (PK)
  • Обработанное событие (event_id, процесс)

Первый представляет события в приложении.

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

Чтобы найти все события, которые нуждаются в обработке, мы выполняем следующий запрос:

select * // of course we do name the columns in the production code
from Event
where type in ( 'typeA', 'typeB', 'typeC')
and id not in (
    select event_id
    from ProcessedEvent
    where process = :1  
)

Статистика актуальна

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

  • полное сканирование индекса по индексу ProcessedEvent
  • полное индексное сканирование индекса события
  • антисоединение между этими двумя
  • доступ к таблице вместе с остальными
  • Фильтр

Вместо этого Oracle выполняет следующее

  • полное сканирование индекса по индексу ProcessedEvent
  • полное сканирование таблицы в таблице событий
  • отфильтруйте таблицу событий
  • антисоединение между двумя наборами

С помощью подсказки index я заставляю Oracle выполнить следующее:

  • полное сканирование индекса по индексу ProcessedEvent
  • полное индексное сканирование индекса события
  • доступ к таблице в таблице событий
  • отфильтруйте таблицу событий
  • антисоединение между двумя наборами

что действительно глупо, ИМХО.

Итак, мой вопрос заключается в следующем:что может быть причиной, по которой oracle настаивает на раннем доступе к таблице?


Дополнение:Производительность плохая.Мы устраняем проблему с производительностью, выбирая только событие.Идентификаторы, а затем извлекая необходимые строки "вручную".Но, конечно, это всего лишь обходной маневр.

Это было полезно?

Решение

ваше ПОЛНОЕ СКАНИРОВАНИЕ ИНДЕКСА, вероятно, будет быстрее, чем ПОЛНОЕ СКАНИРОВАНИЕ ТАБЛИЦЫ, поскольку индекс, скорее всего, "тоньше" таблицы.Тем не менее, ПОЛНОЕ СКАНИРОВАНИЕ ИНДЕКСА - это полное чтение сегмента, и оно будет стоить примерно столько же, сколько ПОЛНОЕ СКАНИРОВАНИЕ ТАБЛИЦЫ.

Однако вы также добавляете ДОСТУП к ТАБЛИЦЕ НА шаге ROWID.Это дорогостоящий шаг:один логический ввод -вывод в строке для доступа к ROWID, тогда как вы получаете один логический ввод-вывод на несколько блоков (в зависимости от вашего db_file_multiblock_read_count parameter) для ПОЛНОГО СКАНИРОВАНИЯ ТАБЛИЦЫ.

В заключение оптимизатор вычисляет, что:

cost(FULL TABLE SCAN) < cost(FULL INDEX SCAN) + cost(TABLE ACCESS BY ROWID)

Обновить:Полное СКАНИРОВАНИЕ ТАБЛИЦЫ также включает фильтр по типу раньше, чем в пути ПОЛНОГО СКАНИРОВАНИЯ ИНДЕКСА (поскольку ИНДЕКС не знает, к какому типу относится событие), что уменьшает размер набора, к которому будет предотвращено объединение (еще одно преимущество ПОЛНОГО СКАНИРОВАНИЯ ТАБЛИЦЫ).

Другие советы

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

Таблицу событий может быть проще сканировать полностью, а не с помощью rowid access из-за ее размера.Возможно, для последовательного чтения всей таблицы требуется значительно меньше операций ввода-вывода, чем для чтения битов и фрагментов.

Это плохая производительность, или вы просто спрашиваете, почему оптимизатор это сделал?

Я не могу объяснить поведение оптимизатора, но мой опыт заключается в том, чтобы любой ценой избегать "НЕ ВХОДИТ", заменяя его на МИНУС, вот так:

select * from Event
where id in (
  select id from Event where type in ( 'typeA', 'typeB', 'typeC')
 minus
  select id from ProcessedEvent
)

Я видел увеличение производительности запросов на порядки при аналогичных преобразованиях.

Что - то вроде:

WITH
  PROCEEDED AS
  (
    SELECT
      event_id
    FROM
      ProcessedEvent
    WHERE
      PROCESS = :1
  )
SELECT
  * // of course we do name the columns in the production code
FROM
  EVENT
LEFT JOIN PROCEEDED P
ON
  p.event_id = EVENT.event_id
WHERE
  type           IN ( 'typeA', 'typeB', 'typeC')
  AND p.event_id IS NULL; -- exclude already proceeded

мог бы работать достаточно быстро (по крайней мере, намного быстрее, чем NOT IN).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top