본문 바로가기
DBMS/SQL튜닝

스칼라서브쿼리 실행계획 순서 및 실행통계

by 드바 2023. 6. 28.
스칼라 서브쿼리 실행계획
- 스칼라 서브 쿼리는 실행 계획 상단에 별도로 표시된다.
- 스칼라 서브 쿼리는 최종 SELECT 목록 순서로 표시되며, 중첩 스칼라 서브 쿼리 안쪽부터 표시된다.

스칼라 서브쿼리의 buffers  수행시간 메인쿼리 실행통계에 포함 여부
- 일반적으로 스칼라 서브 쿼리의 수행 시간과 블록 I/O는 메인쿼리 실행 통계에 포함되지 않는다.
- 인라인뷰와 같은 뷰머징이 발생하지 않을 경우 전체 실행통계에 포함되어 보인다.

메인쿼리 row 여러 개일 스칼라 서브쿼리 실행계획
- 일반상황 스칼라서브쿼리 실행통계는 메인쿼리와 별도로 보여짐
- 인라인뷰로 변경 메인쿼리 실행계획에 스칼라서브쿼리 시간 buffer 포함됨

 

아래 쿼리는 인라인 뷰(t2)와 메인 쿼리(t3)에 스칼라 서브 쿼리를 사용했다.

최종 SELECT 목록 순서(t3 -> t2)대로 스칼라 서브 쿼리가 표시되는 것을 확인할 수 있다.

SELECT (SELECT y.c2
          FROM t3 y
         WHERE y.c1 = a.c1) AS t3
     , a.*
  FROM (SELECT a.*
             , (SELECT x.c2
                  FROM t2 x
                 WHERE x.c1 = a.c1) AS t2
          FROM t1 a) a;

---------------------------------------------
| Id  | Operation                   | Name  |
---------------------------------------------
|   0 | SELECT STATEMENT            |       |
|   1 |  TABLE ACCESS BY INDEX ROWID| T3    |
|*  2 |   INDEX RANGE SCAN          | T3_X1 |
|   3 |  TABLE ACCESS BY INDEX ROWID| T2    |
|*  4 |   INDEX RANGE SCAN          | T2_X1 |
|   5 |  TABLE ACCESS FULL          | T1    |
---------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("Y"."C1"=:B1)
   4 - access("X"."C1"=:B1)

 

아래 쿼리는 스칼라 서브 쿼리를 중첩했다. 중첩 스칼라 서브 쿼리의 안쪽(t3)부터 표시되는 것을 확인할 수 있다.

SELECT a.*
     , (SELECT (SELECT y.c2
                  FROM t3 y
                 WHERE y.c1 = x.c1) AS t4
          FROM t2 x
         WHERE x.c1 = a.c1
           AND x.c2 > 0) AS t3
  FROM t1 a;

---------------------------------------------
| Id  | Operation                   | Name  |
---------------------------------------------
|   0 | SELECT STATEMENT            |       |
|   1 |  TABLE ACCESS BY INDEX ROWID| T3    |
|*  2 |   INDEX RANGE SCAN          | T3_X1 |
|*  3 |  TABLE ACCESS BY INDEX ROWID| T2    |
|*  4 |   INDEX RANGE SCAN          | T2_X1 |
|   5 |  TABLE ACCESS FULL          | T1    |
---------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("Y"."C1"=:B1)
   3 - filter("X"."C2">0)
   4 - access("X"."C1"=:B1)

 

스칼라 서브 쿼리의 소요 시간과 블록 I/O에 대한 내용을 살펴보자.

테스트를 위해 아래와 같이 테이블을 생성하자.

CREATE TABLE sk.t1 AS SELECT 1 AS c1 FROM DUAL;
CREATE TABLE sk.t2 AS SELECT 1 AS c1 FROM DUAL;
CREATE TABLE sk.t3 AS SELECT 1 AS c1 FROM DUAL;
CREATE TABLE sk.t4 AS SELECT 1 AS c1 FROM DUAL;

 

수행 시간 측정을 위해 아래와 같이 함수를 생성하자. 입력한 초(i_seconds)만큼 대기하는 함수다.

GRANT EXECUTE ON DBMS_LOCK TO SK;

CREATE OR REPLACE FUNCTION SK.FNC_SLEEP (I_SECONDS IN NUMBER) RETURN NUMBER
IS
BEGIN
    DBMS_LOCK.SLEEP (I_SECONDS);
    RETURN 1;
END FNC_SLEEP;
/

 

아래 쿼리는 스칼라 서브 쿼리 3개를 사용했다.

메인 쿼리인 t1은 1초, 스칼라 서브 쿼리인 t2, t3, t4는 각각 2초, 3초, 4초가 소요된다.

일반적으로 스칼라 서브 쿼리의 수행 시간과 블록 I/O는 메인 쿼리에 포함되지 않는다.

실행 계획 7번에서 사실을 확인할 수 있다.

SELECT A.*
     , (SELECT 1 FROM T4 WHERE FNC_SLEEP (4) = 1) AS T4 -- T4
  FROM (SELECT A.*
             , (SELECT 1 FROM T3 WHERE FNC_SLEEP (3) = 1) AS T3 -- T3
          FROM (SELECT 1 AS T1
                     , (SELECT 1 FROM T2 WHERE FNC_SLEEP (2) = 1) AS T2 -- T2
                  FROM T1
                 WHERE FNC_SLEEP (1) = 1) A) A
;
----------------------------------------------------------------------------------------------------------------------
| Id  | Operation	   | Name | Starts | E-Rows | Cost (%CPU)| E-Time   | A-Rows |	 A-Time   | Buffers | Reads  |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |	  |	 1 |	    |	 12 (100)|	    |	   1 |00:00:01.00 |	134 |	   3 |
|*  1 |  FILTER 	   |	  |	 1 |	    |		 |	    |	   1 |00:00:02.00 |	  3 |	   1 |
|   2 |   TABLE ACCESS FULL| T2   |	 1 |	  1 |	  3   (0)| 00:00:01 |	   1 |00:00:00.01 |	  3 |	   1 |
|*  3 |  FILTER 	   |	  |	 1 |	    |		 |	    |	   1 |00:00:03.00 |	  3 |	   1 |
|   4 |   TABLE ACCESS FULL| T3   |	 1 |	  1 |	  3   (0)| 00:00:01 |	   1 |00:00:00.01 |	  3 |	   1 |
|*  5 |  FILTER 	   |	  |	 1 |	    |		 |	    |	   1 |00:00:04.00 |	  3 |	   1 |
|   6 |   TABLE ACCESS FULL| T4   |	 1 |	  1 |	  3   (0)| 00:00:01 |	   1 |00:00:00.01 |	  3 |	   1 |
|*  7 |  FILTER 	   |	  |	 1 |	    |		 |	    |	   1 |00:00:01.00 |	134 |	   3 |
|   8 |   TABLE ACCESS FULL| T1   |	 1 |	  1 |	  3   (0)| 00:00:01 |	   1 |00:00:00.01 |	  3 |	   0 |
----------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("FNC_SLEEP"(2)=1)
   3 - filter("FNC_SLEEP"(3)=1)
   5 - filter("FNC_SLEEP"(4)=1)
   7 - filter("FNC_SLEEP"(1)=1)

 

아래 쿼리는 t2 스칼라 서브 쿼리가 포함된 인라인 뷰를 병합(merge)하지 않았다.

실행 계획 7번에서 t2 스칼라 서브 쿼리의 수행 시간과 블록 I/O가 메인 쿼리에 포함된 것을 확인할 수 있다.

SELECT A.*
     , (SELECT 1 FROM T4 WHERE FNC_SLEEP (4) = 1) AS T4
  FROM (SELECT A.*
             , (SELECT 1 FROM T3 WHERE FNC_SLEEP (3) = 1) AS T3
          FROM (SELECT /*+ NO_MERGE */
                       1 AS T1
                     , (SELECT 1 FROM T2 WHERE FNC_SLEEP (2) = 1) AS T2
                  FROM T1
                 WHERE FNC_SLEEP (1) = 1) A) A
;
----------------------------------------------------------------------------------------------------------------------
| Id  | Operation	    | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |	   |	  1 |	     |	     |	  12 (100)|	     |	    1 |00:00:03.00 |	   6 |
|*  1 |  FILTER 	    |	   |	  1 |	     |	     |		  |	     |	    1 |00:00:02.00 |	   3 |
|   2 |   TABLE ACCESS FULL | T2   |	  1 |	   1 |	     |	   3   (0)| 00:00:01 |	    1 |00:00:00.01 |	   3 |
|*  3 |  FILTER 	    |	   |	  1 |	     |	     |		  |	     |	    1 |00:00:03.00 |	   3 |
|   4 |   TABLE ACCESS FULL | T3   |	  1 |	   1 |	     |	   3   (0)| 00:00:01 |	    1 |00:00:00.01 |	   3 |
|*  5 |  FILTER 	    |	   |	  1 |	     |	     |		  |	     |	    1 |00:00:04.00 |	   3 |
|   6 |   TABLE ACCESS FULL | T4   |	  1 |	   1 |	     |	   3   (0)| 00:00:01 |	    1 |00:00:00.01 |	   3 |
|   7 |  VIEW		    |	   |	  1 |	   1 |	   6 |	   6   (0)| 00:00:01 |	    1 |00:00:03.00 |	   6 | -- T1 + T2
|*  8 |   FILTER	    |	   |	  1 |	     |	     |		  |	     |	    1 |00:00:01.00 |	   3 |
|   9 |    TABLE ACCESS FULL| T1   |	  1 |	   1 |	     |	   3   (0)| 00:00:01 |	    1 |00:00:00.01 |	   3 |
----------------------------------------------------------------------------------------------------------------------

 

아래 쿼리는 t3 스칼라 서브 쿼리가 포함된 인라인 뷰를 병합(merge)하지 않았다.

병합되지 않은 인라인 뷰에 포함된 스칼라 서브 쿼리의 수행 시간과 블록 I/O가 메인 쿼리에 포함된다.

실행 계획 7번에서 t2, t3 스칼라 서브 쿼리의 수행 시간과 블록 I/O가 메인 쿼리에 포함된 것을 확인할 수 있다.

SELECT A.*
     , (SELECT 1 FROM T4 WHERE FNC_SLEEP (4) = 1) AS T4
  FROM (SELECT /*+ NO_MERGE */
               A.*
             , (SELECT 1 FROM T3 WHERE FNC_SLEEP (3) = 1) AS T3
          FROM (SELECT 1 AS T1
                     , (SELECT 1 FROM T2 WHERE FNC_SLEEP (2) = 1) AS T2
                  FROM T1
                 WHERE FNC_SLEEP (1) = 1) A) A
;
----------------------------------------------------------------------------------------------------------------------
| Id  | Operation	    | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |	   |	  1 |	     |	     |	  12 (100)|	     |	    1 |00:00:06.00 |	   9 |
|*  1 |  FILTER 	    |	   |	  1 |	     |	     |		  |	     |	    1 |00:00:02.00 |	   3 |
|   2 |   TABLE ACCESS FULL | T2   |	  1 |	   1 |	     |	   3   (0)| 00:00:01 |	    1 |00:00:00.01 |	   3 |
|*  3 |  FILTER 	    |	   |	  1 |	     |	     |		  |	     |	    1 |00:00:03.00 |	   3 |
|   4 |   TABLE ACCESS FULL | T3   |	  1 |	   1 |	     |	   3   (0)| 00:00:01 |	    1 |00:00:00.01 |	   3 |
|*  5 |  FILTER 	    |	   |	  1 |	     |	     |		  |	     |	    1 |00:00:04.00 |	   3 |
|   6 |   TABLE ACCESS FULL | T4   |	  1 |	   1 |	     |	   3   (0)| 00:00:01 |	    1 |00:00:00.01 |	   3 |
|   7 |  VIEW		    |	   |	  1 |	   1 |	   9 |	   9   (0)| 00:00:01 |	    1 |00:00:06.00 |	   9 | -- T1 + T2 + T3
|*  8 |   FILTER	    |	   |	  1 |	     |	     |		  |	     |	    1 |00:00:01.00 |	   3 |
|   9 |    TABLE ACCESS FULL| T1   |	  1 |	   1 |	     |	   3   (0)| 00:00:01 |	    1 |00:00:00.01 |	   3 |
----------------------------------------------------------------------------------------------------------------------

 

아래 쿼리는 t2, t3 스칼라 서브 쿼리가 포함된 인라인 뷰를 병합(merge)하지 않았다.

실행 계획 7, 8번에서 스칼라 서브 쿼리의 수행 시간과 블록 I/O가 인라인 뷰에 각각 포함된 것을 확인할 수 있다.

SELECT A.*
     , (SELECT 1 FROM T4 WHERE FNC_SLEEP (4) = 1) AS T4
  FROM (SELECT /*+ NO_MERGE */
               A.*
             , (SELECT 1 FROM T3 WHERE FNC_SLEEP (3) = 1) AS T3
          FROM (SELECT /*+ NO_MERGE */
                       1 AS T1
                     , (SELECT 1 FROM T2 WHERE FNC_SLEEP (2) = 1) AS T2
                  FROM T1
                 WHERE FNC_SLEEP (1) = 1) A) A
;
-----------------------------------------------------------------------------------------------------------------------
| Id  | Operation	     | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |	    |	   1 |	      |       |    12 (100)|	      |      1 |00:00:06.00 |	    9 |
|*  1 |  FILTER 	     |	    |	   1 |	      |       | 	   |	      |      1 |00:00:02.00 |	    3 |
|   2 |   TABLE ACCESS FULL  | T2   |	   1 |	    1 |       |     3	(0)| 00:00:01 |      1 |00:00:00.01 |	    3 |
|*  3 |  FILTER 	     |	    |	   1 |	      |       | 	   |	      |      1 |00:00:03.00 |	    3 |
|   4 |   TABLE ACCESS FULL  | T3   |	   1 |	    1 |       |     3	(0)| 00:00:01 |      1 |00:00:00.01 |	    3 |
|*  5 |  FILTER 	     |	    |	   1 |	      |       | 	   |	      |      1 |00:00:04.00 |	    3 |
|   6 |   TABLE ACCESS FULL  | T4   |	   1 |	    1 |       |     3	(0)| 00:00:01 |      1 |00:00:00.01 |	    3 |
|   7 |  VIEW		     |	    |	   1 |	    1 |     9 |     9	(0)| 00:00:01 |      1 |00:00:06.00 |	    9 | -- T1 + T2 + T3
|   8 |   VIEW		     |	    |	   1 |	    1 |     6 |     6	(0)| 00:00:01 |      1 |00:00:03.00 |	    6 | -- T1 + T2
|*  9 |    FILTER	     |	    |	   1 |	      |       | 	   |	      |      1 |00:00:01.00 |	    3 |
|  10 |     TABLE ACCESS FULL| T1   |	   1 |	    1 |       |     3	(0)| 00:00:01 |      1 |00:00:00.01 |	    3 |
-----------------------------------------------------------------------------------------------------------------------

 

아래 쿼리는 쿼리 블록을 추가해 t4 스칼라 서브 쿼리가 포함된 인라인 뷰까지 병합(merge)하지 않았다.

실행 계획 7번에서 전체 스칼라 서브 쿼리의 수행 시간과 블록 I/O가 포함된 값을 확인할 수 있다.

SELECT *
  FROM (SELECT /*+ NO_MERGE */
               A.*
             , (SELECT 1 FROM T4 WHERE FNC_SLEEP (4) = 1) AS T4
          FROM (SELECT /*+ NO_MERGE */
                       A.*
                     , (SELECT 1 FROM T3 WHERE FNC_SLEEP (3) = 1) AS T3
                  FROM (SELECT /*+ NO_MERGE */
                               1 AS T1
                             , (SELECT 1 FROM T2 WHERE FNC_SLEEP (2) = 1) AS T2
                          FROM T1
                         WHERE FNC_SLEEP (1) = 1) A) A)
;
------------------------------------------------------------------------------------------------------------------------
| Id  | Operation	      | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |	    1 |        |       |    12 (100)|	       |      1 |00:00:10.00 |	    12 |
|*  1 |  FILTER 	      |      |	    1 |        |       |	    |	       |      1 |00:00:02.00 |	     3 |
|   2 |   TABLE ACCESS FULL   | T2   |	    1 |      1 |       |     3	 (0)| 00:00:01 |      1 |00:00:00.01 |	     3 |
|*  3 |  FILTER 	      |      |	    1 |        |       |	    |	       |      1 |00:00:03.00 |	     3 |
|   4 |   TABLE ACCESS FULL   | T3   |	    1 |      1 |       |     3	 (0)| 00:00:01 |      1 |00:00:00.01 |	     3 |
|*  5 |  FILTER 	      |      |	    1 |        |       |	    |	       |      1 |00:00:04.00 |	     3 |
|   6 |   TABLE ACCESS FULL   | T4   |	    1 |      1 |       |     3	 (0)| 00:00:01 |      1 |00:00:00.01 |	     3 |
|   7 |  VIEW		      |      |	    1 |      1 |    12 |    12	 (0)| 00:00:01 |      1 |00:00:10.00 |	    12 | -- T1 + T2 + T3 + T4
|   8 |   VIEW		      |      |	    1 |      1 |     9 |     9	 (0)| 00:00:01 |      1 |00:00:06.00 |	     9 | -- T1 + T2 + T3
|   9 |    VIEW 	      |      |	    1 |      1 |     6 |     6	 (0)| 00:00:01 |      1 |00:00:03.00 |	     6 | -- T1 + T2
|* 10 |     FILTER	      |      |	    1 |        |       |	    |	       |      1 |00:00:01.00 |	     3 |
|  11 |      TABLE ACCESS FULL| T1   |	    1 |      1 |       |     3	 (0)| 00:00:01 |      1 |00:00:00.01 |	     3 |
------------------------------------------------------------------------------------------------------------------------

 

메인쿼리 row 여러 개일 스칼라 서브쿼리 실행계획

-- 테스트 테이블 생성 5개 row
CREATE TABLE SK.TAB100 AS 
SELECT LEVEL LVL 
FROM DUAL CONNECT BY LEVEL <= 5
;

 

일반상황 스칼라서브쿼리 실행통계는 메인쿼리와 별도로 보여짐

1+2+3+4+5 = 15 5 수행 되는 확인

SELECT A.*, (SELECT 1 FROM SK.T2 WHERE SK.FNC_SLEEP(A.LVL) = 1) AS C1 
FROM SK.TAB100 A
WHERE ROWNUM <= 5
;
-----------------------------------------------------------------------------------------------------------------------
| Id  | Operation	   | Name   | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |	    |	   1 |	      |       |    10 (100)|	      |      5 |00:00:00.01 |	    3 |
|*  1 |  FILTER 	   |	    |	   5 |	      |       | 	   |	      |      5 |00:00:15.00 |	   10 |
|   2 |   TABLE ACCESS FULL| T2     |	   5 |	    1 |       |     3	(0)| 00:00:01 |      5 |00:00:00.01 |	   10 |
|*  3 |  COUNT STOPKEY	   |	    |	   1 |	      |       | 	   |	      |      5 |00:00:00.01 |	    3 |
|   4 |   TABLE ACCESS FULL| TAB100 |	   1 |	    5 |    15 |     2	(0)| 00:00:01 |      5 |00:00:00.01 |	    3 |
-----------------------------------------------------------------------------------------------------------------------

 

인라인뷰로 변경 메인쿼리 실행계획에 스칼라서브쿼리 시간 buffer 포함됨

SELECT * FROM (
SELECT A.*, (SELECT 1 FROM SK.T2 WHERE SK.FNC_SLEEP(A.LVL) = 1) AS C1 
FROM SK.TAB100 A
WHERE ROWNUM <= 5
)
;
------------------------------------------------------------------------------------------------------------------------
| Id  | Operation	    | Name   | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |	     |	    1 |        |       |    10 (100)|	       |      5 |00:00:15.00 |	    13 |
|*  1 |  FILTER 	    |	     |	    5 |        |       |	    |	       |      5 |00:00:15.00 |	    10 |
|   2 |   TABLE ACCESS FULL | T2     |	    5 |      1 |       |     3	 (0)| 00:00:01 |      5 |00:00:00.01 |	    10 |
|   3 |  VIEW		    |	     |	    1 |      5 |    80 |    10	 (0)| 00:00:01 |      5 |00:00:15.00 |	    13 |
|*  4 |   COUNT STOPKEY     |	     |	    1 |        |       |	    |	       |      5 |00:00:00.01 |	     3 |
|   5 |    TABLE ACCESS FULL| TAB100 |	    1 |      5 |    15 |     2	 (0)| 00:00:01 |      5 |00:00:00.01 |	     3 |
------------------------------------------------------------------------------------------------------------------------

댓글