문제

우리는 다음과 같은 두 개의 테이블이 있습니다.

Event
    id
    type
    ... a bunch of other columns

ProcessedEvent
    event_id
    process

정의 된 색인이 있습니다

  • 이벤트 (ID) (PK)
  • ProcessedEvent (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 인덱스의 전체 인덱스 스캔
  • 이벤트 테이블의 전체 테이블 스캔
  • 이벤트 테이블을 필터링하십시오
  • 두 세트 사이의 안티 조인

색인 힌트를 사용하면 Oracle이 다음을 수행하게합니다.

  • ProcessedEvent 인덱스의 전체 인덱스 스캔
  • 이벤트 인덱스의 전체 인덱스 스캔
  • 이벤트 테이블의 테이블 Acces
  • 이벤트 테이블을 필터링하십시오
  • 두 세트 사이의 안티 조인

정말 어리석은 IMHO입니다.

제 질문은 : Oracle이 초기 테이블 액세스를 고집 해야하는 이유는 무엇입니까?


추가 : 성능이 나쁩니다. event.ids 만 선택한 다음 필요한 행을 '수동으로'가져와 성능 문제를 해결하고 있습니다. 그러나 물론 그것은 단지 주변의 작업 일뿐입니다.

도움이 되었습니까?

해결책

전체 색인 스캔은 인덱스가 테이블보다 "얇아"될 수 있으므로 전체 테이블 스캔보다 빠를 것입니다. 그럼에도 불구하고 전체 인덱스 스캔은 전체 세그먼트 판독 값이며 전체 테이블 스캔과 거의 같은 비용이됩니다.

그러나 Rowid 단계에서 테이블 액세스를 추가하고 있습니다. 비싼 단계입니다 : 하나의 논리적 io 행당 Rowid 액세스의 경우 하나의 논리적 IO를 얻을 수 있습니다. 멀티 블록 당 (당신에 따라 db_file_multiblock_read_count parameter) 전체 테이블 스캔.

결론적으로, Optimizer는 다음을 계산합니다.

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

업데이트: 전체 테이블 스캔은 또한 전체 인덱스 스캔 경로보다 더 빨리 유형의 필터를 활성화 할 수 있습니다 (인덱스가 이벤트가 어떤 유형인지 알지 못하기 때문에), 안티 조정이 될 세트의 크기가 줄어 듭니다. 전체 테이블 스캔의 장점).

다른 팁

Optimizer는 처음에는 의미가없는 많은 일을하지만 그 이유가 있습니다. 그들은 항상 그런 것은 아닙니다 오른쪽, 그러나 그들은 이해할 수 있습니다.

이벤트 테이블은 크기로 인해 Rowid 액세스가 아닌 풀 스캔이 더 쉬울 수 있습니다. 비트와 조각을 읽는 것보다 순차적으로 전체 테이블을 읽는 데 관련된 IO 작업이 상당히 적을 수 있습니다.

성능이 나쁘거나 옵티마이저가 왜 그렇게했는지 묻는 건가요?

나는 옵티마이저의 행동을 설명 할 수 없지만, 내 경험은 모든 비용으로 "안개가 아님"을 피하고 대신 마이너스로 대체하는 것이 었습니다.

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