쿼리 변환을 통해 Top-N 쿼리의 성능을 개선한 사례를 살펴보자.
아래와 같이 테이블을 생성하자.
아래는 t1, t2 테이블을 아우터 조인한 Top-N 쿼리다. t1, t2 테이블이 해시 조인되어 418개의 블록 I/O가 발생했다.
2번 쿼리는 아우터 조인한 t2 테이블의 c1 칼럼이 PK이므로 아래 쿼리처럼 Top-N 처리 후 t2 테이블을 조인해도 결과가 동일하다. t2 테이블의 NL 조인되어 블록 I/O가 229개로 감소했다.
아래와 같이 쿼리를 변경해보자. 인라인 뷰의 SELECT 목록을 t1 테이블만 조회했고, Top-N 처리 후에 t2 테이블을 아우터 조인했다. 중복 조인으로 인해 블록 I/O가 5번 쿼리보다 20개 증가했다. 전체 블록 I/O에 비해 크지 않은 수치로 볼 수 있다.
스칼라 서브 쿼리도 동일한 기법을 활용할 수 있다. 아래 쿼리는 동적 조건에 스칼라 서브 쿼리의 결과를 사용했다. 101,000개의 블록 I/O가 발생했다.
-- 1 DROP TABLE t1 PURGE; DROP TABLE t2 PURGE; CREATE TABLE t1 AS SELECT ROWNUM AS c1, ROWNUM AS c2 FROM XMLTABLE ('1 to 100000'); CREATE TABLE t2 AS SELECT * FROM t1; ALTER TABLE t2 ADD CONSTRAINT t2_pk PRIMARY KEY (c1);
-- 2 SELECT * FROM (SELECT * FROM t1 a LEFT OUTER JOIN t2 b ON b.c1 = a.c2 WHERE a.c1 > 0 ORDER BY a.c1) a WHERE ROWNUM <= 10; -------------------------------------------------------------------- | Id | Operation | Name | Starts | A-Rows | Buffers | -------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 10 | 418 | |* 1 | COUNT STOPKEY | | 1 | 10 | 418 | | 2 | VIEW | | 1 | 10 | 418 | |* 3 | SORT ORDER BY STOPKEY| | 1 | 10 | 418 | |* 4 | HASH JOIN OUTER | | 1 | 100K| 418 | |* 5 | TABLE ACCESS FULL | T1 | 1 | 100K| 209 | | 6 | TABLE ACCESS FULL | T2 | 1 | 100K| 209 | --------------------------------------------------------------------
-- 3 SELECT * FROM (SELECT * FROM t1 a WHERE a.c1 > 0 ORDER BY a.c1) a LEFT OUTER JOIN t2 b ON b.c1 = a.c2 WHERE ROWNUM <= 10; --------------------------------------------------------------------------- | Id | Operation | Name | Starts | A-Rows | Buffers | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 10 | 229 | |* 1 | COUNT STOPKEY | | 1 | 10 | 229 | | 2 | NESTED LOOPS OUTER | | 1 | 10 | 229 | | 3 | VIEW | | 1 | 10 | 209 | | 4 | SORT ORDER BY | | 1 | 10 | 209 | |* 5 | TABLE ACCESS FULL | T1 | 1 | 100K| 209 | | 6 | TABLE ACCESS BY INDEX ROWID| T2 | 10 | 10 | 20 | |* 7 | INDEX UNIQUE SCAN | T2_PK | 10 | 10 | 10 | ---------------------------------------------------------------------------
아래와 같이 t2 테이블에 대한 동적 조건이 존재한다면 쿼리를 3번 쿼리처럼 변경할 수 없다. 동적 조건과 함께 아우터 조인까지 동적으로 처리하는 방식이 성능 측면에서 가장 효율적이지만 개발 난이도가 높아지는 단점이 있다.
-- 4 SELECT * FROM (SELECT * FROM t1 a LEFT OUTER JOIN t2 b ON b.c1 = a.c2 WHERE a.c1 > 0 AND b.c2 > 0 -- 동적 조건 ORDER BY a.c1) a WHERE ROWNUM <= 10; -------------------------------------------------------------------- | Id | Operation | Name | Starts | A-Rows | Buffers | -------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 10 | 418 | |* 1 | COUNT STOPKEY | | 1 | 10 | 418 | | 2 | VIEW | | 1 | 10 | 418 | |* 3 | SORT ORDER BY STOPKEY| | 1 | 10 | 418 | |* 4 | HASH JOIN | | 1 | 100K| 418 | |* 5 | TABLE ACCESS FULL | T1 | 1 | 100K| 209 | |* 6 | TABLE ACCESS FULL | T2 | 1 | 100K| 209 | --------------------------------------------------------------------
-- 5 SELECT * FROM (SELECT a.* -- 변경 FROM t1 a LEFT OUTER JOIN t2 b ON b.c1 = a.c2 WHERE a.c1 > 0 AND b.c2 > 0 -- 동적 조건 ORDER BY a.c1) a LEFT OUTER JOIN t2 b ON b.c1 = a.c2 WHERE ROWNUM <= 10; --------------------------------------------------------------------------- | Id | Operation | Name | Starts | A-Rows | Buffers | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 10 | 438 | |* 1 | COUNT STOPKEY | | 1 | 10 | 438 | | 2 | NESTED LOOPS OUTER | | 1 | 10 | 438 | | 3 | VIEW | | 1 | 10 | 418 | | 4 | SORT ORDER BY | | 1 | 10 | 418 | |* 5 | HASH JOIN | | 1 | 100K| 418 | |* 6 | TABLE ACCESS FULL | T1 | 1 | 100K| 209 | |* 7 | TABLE ACCESS FULL | T2 | 1 | 100K| 209 | | 8 | TABLE ACCESS BY INDEX ROWID| T2 | 10 | 10 | 20 | |* 9 | INDEX UNIQUE SCAN | T2_PK | 10 | 10 | 10 | ---------------------------------------------------------------------------
아래 쿼리는 동적 조건이 비활성화된 경우를 가정하여 동적 조건을 주석 처리했다. 쿼리 변환에 의해 인라인 뷰 내에서 t2 테이블의 조인이 제거되었다. 블록 I/O가 3번 쿼리와 동일하게 229개만 발생한다. 쿼리 변환을 통해 동적 조건이 비활성화된 경우 블록 I/O를 감소시키는 개선 방안이다.
-- 6 SELECT * FROM (SELECT a.* -- 변경 FROM t1 a LEFT OUTER JOIN t2 b ON b.c1 = a.c2 WHERE a.c1 > 0 -- AND b.c2 > 0 -- 동적 조건 ORDER BY a.c1) a LEFT OUTER JOIN t2 b ON b.c1 = a.c2 WHERE ROWNUM <= 10; --------------------------------------------------------------------------- | Id | Operation | Name | Starts | A-Rows | Buffers | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 10 | 229 | |* 1 | COUNT STOPKEY | | 1 | 10 | 229 | | 2 | NESTED LOOPS OUTER | | 1 | 10 | 229 | | 3 | VIEW | | 1 | 10 | 209 | | 4 | SORT ORDER BY | | 1 | 10 | 209 | |* 5 | TABLE ACCESS FULL | T1 | 1 | 100K| 209 | | 6 | TABLE ACCESS BY INDEX ROWID| T2 | 10 | 10 | 20 | |* 7 | INDEX UNIQUE SCAN | T2_PK | 10 | 10 | 10 | ---------------------------------------------------------------------------
-- 7 SELECT a.* FROM (SELECT a.* , (SELECT MAX (x.c2) FROM t2 x WHERE x.c1 = a.c2) AS xc2 FROM t1 a ORDER BY a.c1) a WHERE ROWNUM <= 10 AND a.xc2 > 0 -- 동적 조건 ; -------------------------------------------------------------------------- | Id | Operation | Name | Starts | A-Rows | Buffers | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 10 | 101K| | 1 | SORT AGGREGATE | | 100K| 100K| 101K| | 2 | TABLE ACCESS BY INDEX ROWID| T2 | 100K| 100K| 101K| |* 3 | INDEX UNIQUE SCAN | T2_PK | 100K| 100K| 1462 | |* 4 | COUNT STOPKEY | | 1 | 10 | 101K| |* 5 | VIEW | | 1 | 10 | 101K| | 6 | SORT ORDER BY | | 1 | 10 | 101K| | 7 | TABLE ACCESS FULL | T1 | 1 | 100K| 209 | --------------------------------------------------------------------------
아래와 같이 쿼리를 변경해보자. 메인 쿼리의 SELECT 목록에서 t1 테이블만 조회했고, 스칼라 서브 쿼리를 중복 기술했다. 중복 스칼라 서브 쿼리로 인해 블록 I/O가 7번 쿼리보다 20개 증가했다.
-- 8 SELECT a.c1 -- 변경 , a.c2 -- 변경 , (SELECT MAX (x.c2) FROM t2 x WHERE x.c1 = a.c2) AS xc2 FROM (SELECT a.* , (SELECT MAX (x.c2) FROM t2 x WHERE x.c1 = a.c2) AS xc2 FROM t1 a ORDER BY a.c1) a WHERE ROWNUM <= 10 AND a.xc2 > 0 -- 동적 조건 ; ---------------------------------------------------------------------------- | Id | Operation | Name | Starts | A-Rows | Buffers | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 10 | 101K| | 1 | SORT AGGREGATE | | 10 | 10 | 20 | | 2 | TABLE ACCESS BY INDEX ROWID | T2 | 10 | 10 | 20 | |* 3 | INDEX UNIQUE SCAN | T2_PK | 10 | 10 | 10 | |* 4 | COUNT STOPKEY | | 1 | 10 | 101K| |* 5 | VIEW | | 1 | 10 | 101K| | 6 | SORT AGGREGATE | | 100K| 100K| 101K| | 7 | TABLE ACCESS BY INDEX ROWID| T2 | 100K| 100K| 101K| |* 8 | INDEX UNIQUE SCAN | T2_PK | 100K| 100K| 1462 | | 9 | SORT ORDER BY | | 1 | 10 | 101K| | 10 | TABLE ACCESS FULL | T1 | 1 | 100K| 209 | ----------------------------------------------------------------------------
아래 쿼리는 동적 조건을 주석 처리했다. 아우터 조인과 마찬가지로 쿼리 변환에 의해 스칼라 서브 쿼리가 제거되어 블록 I/O가 209개만 발생한다.
-- 9 SELECT a.c1 -- 변경 , a.c2 -- 변경 , (SELECT MAX (x.c2) FROM t2 x WHERE x.c1 = a.c2) AS c2x FROM (SELECT a.* , (SELECT MAX (x.c2) FROM t2 x WHERE x.c1 = a.c2) AS xc2 FROM t1 a ORDER BY a.c1) a WHERE ROWNUM <= 10 -- AND a.xc2 > 0 -- 동적 조건 ; -------------------------------------------------------------------------- | Id | Operation | Name | Starts | A-Rows | Buffers | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 10 | 209 | | 1 | SORT AGGREGATE | | 10 | 10 | 20 | | 2 | TABLE ACCESS BY INDEX ROWID| T2 | 10 | 10 | 20 | |* 3 | INDEX UNIQUE SCAN | T2_PK | 10 | 10 | 10 | |* 4 | COUNT STOPKEY | | 1 | 10 | 209 | | 5 | VIEW | | 1 | 10 | 209 | |* 6 | SORT ORDER BY STOPKEY | | 1 | 10 | 209 | | 7 | TABLE ACCESS FULL | T1 | 1 | 100K| 209 | --------------------------------------------------------------------------
'Oracle > Tuning' 카테고리의 다른 글
여부 속성으로 인한 성능 저하 사례 #2 (0) | 2020.07.09 |
---|---|
여부 속성으로 인한 성능 저하 사례 #1 (0) | 2020.07.08 |
뷰 병합이 동작하지 않는 사례 (0) | 2020.04.07 |
조인에 따른 사용자 함수의 동작 (0) | 2020.03.21 |
월 기준 이력 조회 (0) | 2020.03.16 |