뷰 병합이 동작하지 않아 쿼리 성능이 저하된 사례를 살펴보자.
테스트를 위해 아래와 같이 테이블과 뷰를 생성하자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
-- 1 DROP TABLE t1 PURGE; DROP TABLE t2 PURGE; DROP TABLE t3 PURGE; CREATE TABLE t1 AS SELECT ROWNUM AS c1, ROWNUM AS c2 FROM XMLTABLE ( '1 to 10000' ); CREATE TABLE t2 AS SELECT * FROM t1; CREATE TABLE t3 AS SELECT * FROM t1; CREATE UNIQUE INDEX t1_u1 ON t1 (c2); CREATE UNIQUE INDEX t2_u1 ON t2 (c1); CREATE UNIQUE INDEX t3_u1 ON t3 (c1); CREATE OR REPLACE VIEW v1 AS SELECT * FROM t2; |
아래 쿼리는 테이블과 뷰를 조인한다. v1 뷰가 병합된 것을 확인할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
-- 2 SELECT * FROM t1 a, v1 b WHERE a.c2 = 1 AND b.c1 = a.c1; ----------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E- Rows | A- Rows | Buffers | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 | 6 | | 1 | NESTED LOOPS | | 1 | 1 | 1 | 6 | | 2 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1 | 1 | 3 | |* 3 | INDEX UNIQUE SCAN | T1_U1 | 1 | 1 | 1 | 2 | | 4 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 1 | 1 | 3 | |* 5 | INDEX UNIQUE SCAN | T2_U1 | 1 | 1 | 1 | 2 | ----------------------------------------------------------------------------------- |
아래와 같이 뷰에 스칼라 서브 쿼리와 스칼라 서브 쿼리의 결과를 사용한 필터 조건을 추가해보자.
1 2 3 4 5 6 7 8 9 |
-- 3 CREATE OR REPLACE VIEW v1 AS SELECT * FROM ( SELECT a.c1 , ( SELECT x.c2 FROM t3 x WHERE x.c1 = a.c2) AS c2 --> ADD FROM t2 a) WHERE c2 = 1 --> ADD ; |
쿼리를 다시 수행해보면 v1 뷰가 병합되지 않은 것을 확인할 수 있다. 스칼라 서브 쿼리가 10,000번 수행되어 블록 I/O가 10,175개로 증가했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-- 4 SELECT /*+ MERGE(B) */ * FROM t1 a, v1 b WHERE a.c2 = 1 AND b.c1 = a.c1; ----------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E- Rows | A- Rows | Buffers | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 | 10175 | | 1 | TABLE ACCESS BY INDEX ROWID | T3 | 10000 | 1 | 10000 | 10142 | |* 2 | INDEX UNIQUE SCAN | T3_U1 | 10000 | 1 | 10000 | 142 | | 3 | NESTED LOOPS | | 1 | 1 | 1 | 10175 | | 4 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1 | 1 | 3 | |* 5 | INDEX UNIQUE SCAN | T1_U1 | 1 | 1 | 1 | 2 | |* 6 | VIEW | | 1 | 1 | 1 | 10172 | | 7 | TABLE ACCESS FULL | T2 | 1 | 10000 | 10000 | 30 | ----------------------------------------------------------------------------------- |
힌트를 추가해도 JPPD가 동작하지 않는 것을 확인할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-- 5 SELECT /*+ LEADING(A) USE_NL(B) PUSH_PRED(B) */ * FROM t1 a, v1 b WHERE a.c2 = 1 AND b.c1 = a.c1; ----------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E- Rows | A- Rows | Buffers | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 | 10175 | | 1 | TABLE ACCESS BY INDEX ROWID | T3 | 10000 | 1 | 10000 | 10142 | |* 2 | INDEX UNIQUE SCAN | T3_U1 | 10000 | 1 | 10000 | 142 | | 3 | NESTED LOOPS | | 1 | 1 | 1 | 10175 | | 4 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1 | 1 | 3 | |* 5 | INDEX UNIQUE SCAN | T1_U1 | 1 | 1 | 1 | 2 | |* 6 | VIEW | | 1 | 1 | 1 | 10172 | | 7 | TABLE ACCESS FULL | T2 | 1 | 10000 | 10000 | 30 | ----------------------------------------------------------------------------------- |
조인을 아우터 조인으로 변경하면 JPPD가 동작하지만, 뷰를 사용하는 쿼리가 다수인 경우 근본적인 해법이 될 수 없다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
-- 6 SELECT /*+ LEADING(A) USE_NL(B) PUSH_PRED(B) NO_OUTER_JOIN_TO_INNER */ * FROM t1 a, v1 b WHERE a.c2 = 1 AND b.c1(+) = a.c1 AND b.c1 IS NOT NULL ; ------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E- Rows | A- Rows | Buffers | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 | 9 | | 1 | TABLE ACCESS BY INDEX ROWID | T3 | 1 | 1 | 1 | 3 | |* 2 | INDEX UNIQUE SCAN | T3_U1 | 1 | 1 | 1 | 2 | |* 3 | FILTER | | 1 | | 1 | 9 | | 4 | NESTED LOOPS OUTER | | 1 | 1 | 1 | 9 | | 5 | TABLE ACCESS BY INDEX ROWID | T1 | 1 | 1 | 1 | 3 | |* 6 | INDEX UNIQUE SCAN | T1_U1 | 1 | 1 | 1 | 2 | |* 7 | VIEW PUSHED PREDICATE | | 1 | 1 | 1 | 6 | | 8 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 1 | 1 | 3 | |* 9 | INDEX UNIQUE SCAN | T2_U1 | 1 | 1 | 1 | 2 | ------------------------------------------------------------------------------------- |
해당 현상은 스칼라 서브 쿼리의 결과를 필터 조건으로 사용했기 때문이다. t3 테이블의 c1 열이 고유하므로 뷰의 스칼라 서브 쿼리를 조인으로 변경할 수 있다.
1 2 3 4 5 6 7 |
-- 7 CREATE OR REPLACE VIEW v1 AS SELECT a.c1, b.c2 FROM t2 a, t3 b WHERE b.c1(+) = a.c2 AND b.c2(+) = 1; |
v1 뷰가 병합되어 블록 I/O가 9개로 감소한 것을 확인할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-- 8 SELECT * FROM t1 a, v1 b WHERE a.c2 = 1 AND b.c1 = a.c1; ------------------------------------------------------------------------------------ | Id | Operation | Name | Starts | E- Rows | A- Rows | Buffers | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | | 1 | 9 | | 1 | NESTED LOOPS OUTER | | 1 | 1 | 1 | 9 | | 2 | NESTED LOOPS | | 1 | 1 | 1 | 6 | | 3 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1 | 1 | 3 | |* 4 | INDEX UNIQUE SCAN | T1_U1 | 1 | 1 | 1 | 2 | | 5 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 1 | 1 | 3 | |* 6 | INDEX UNIQUE SCAN | T2_U1 | 1 | 1 | 1 | 2 | |* 7 | TABLE ACCESS BY INDEX ROWID | T3 | 1 | 1 | 1 | 3 | |* 8 | INDEX UNIQUE SCAN | T3_U1 | 1 | 1 | 1 | 2 | ------------------------------------------------------------------------------------ |
'Oracle > Tuning' 카테고리의 다른 글
여부 속성으로 인한 성능 저하 사례 #2 (0) | 2020.07.09 |
---|---|
여부 속성으로 인한 성능 저하 사례 #1 (0) | 2020.07.08 |
조인에 따른 사용자 함수의 동작 (0) | 2020.03.21 |
월 기준 이력 조회 (0) | 2020.03.16 |
INDEX 힌트의 세 가지 방식 (0) | 2020.02.23 |