오라클 반 결합 실행 계획 질문
-
20-09-2019 - |
문제
우리는 다음과 같은 두 개의 테이블이 있습니다.
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
).