참조 문서 : http://scidb.tistory.com/entry/Parallel-Query-의-조인시-Row-Distribution
TEST 스크립트 준비 |
---|
{CODE:SQL} |
SQL> select * from v$version where rownum <= 1
2 ;
BANNER
SQL>
SQL> CREATE TABLE EMP_PARTITION2(
2 EMPNO NUMBER NOT NULL,
3 ENAME VARCHAR2(10),
4 JOB VARCHAR2(9),
5 MGR NUMBER(4),
6 HIREDATE DATE,
7 SAL NUMBER(7, 2),
8 COMM NUMBER(7, 2),
9 DEPTNO NUMBER(2))
10 PARTITION BY RANGE (DEPTNO) (
11 PARTITION P1 VALUES LESS THAN( 20 )
12 , PARTITION P2 VALUES LESS THAN( 30 )
13 , PARTITION P3 VALUES LESS THAN( 40 )
14 , PARTITION P4 VALUES LESS THAN( 50 )
15 );
SQL> SELECT LEVEL AS EMPNO
2 , 'TEST' AS ENAME
3 , 'JOB' AS JOB
4 , 1000 AS MGR
5 , SYSDATE AS HIREDATE
6 , FLOOR( DBMS_RANDOM.VALUE ) * 10000 AS SAL
7 , FLOOR( DBMS_RANDOM.VALUE ) * 100 AS COMM
8 , MOD( LEVEL, 4 ) * 10 + 10 AS DEPTNO
9 FROM DUAL
10 CONNECT BY LEVEL <= 10;
EMPNO ENAM JOB MGR HIREDATE SAL COMM DEPTNO
10 개의 행이 선택되었습니다.
SQL> INSERT INTO EMP_PARTITION2
2 SELECT LEVEL AS EMPNO
3 , 'TEST' AS ENAME
4 , 'JOB' AS JOB
5 , 1000 AS MGR
6 , SYSDATE AS HIREDATE
7 , FLOOR( DBMS_RANDOM.VALUE ) * 10000 AS SAL
8 , FLOOR( DBMS_RANDOM.VALUE ) * 100 AS COMM
9 , MOD( LEVEL, 4 ) * 10 + 10 AS DEPTNO
10 FROM DUAL
11 CONNECT BY LEVEL <= 4000000;
4000000 개의 행이 만들어졌습니다.
SQL> commit;
SQL> CREATE TABLE DEPT_PARTITION(
2 DEPTNO NUMBER(2)
3 , DNAME VARCHAR2(14)
4 , LOC VARCHAR2(13))
5 PARTITION BY RANGE ( DEPTNO) (
6 PARTITION P1 VALUES LESS THAN( 20 )
7 , PARTITION P2 VALUES LESS THAN( 30 )
8 , PARTITION P3 VALUES LESS THAN( 40 )
9 , PARTITION P4 VALUES LESS THAN( 50 )
10 );
테이블이 생성되었습니다.
SQL> INSERT INTO DEPT_PARTITION VALUES (10, 'ACCOUNTING', 'NEW YORK');
SQL> INSERT INTO DEPT_PARTITION VALUES (20, 'RESEARCH', 'DALLAS');
SQL> INSERT INTO DEPT_PARTITION VALUES (30, 'SALES', 'CHICAGO');
SQL> INSERT INTO DEPT_PARTITION VALUES (40, 'OPERATIONS', 'BOSTON');
SQL> COMMIT;
{CODE} | RANGE PARTITION.... |
---|---|
{CODE:SQL} SQL> SELECT /*+ gather_plan_statistics FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) /COUNT() 2 FROM DEPT_PARTITION D, EMP_PARTITION2 E 3 WHERE E.DEPTNO = D.DEPTNO; |
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:03.67 | 20 | |
2 | PX COORDINATOR | 1 | 2 | 00:00:03.57 | 20 | ||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 |
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |
5 | NESTED LOOPS | 0 | 4351K | 0 | 00:00:00.01 | 0 | |
6 | PX RECEIVE | 0 | 0 | 00:00:00.01 | 0 | ||
7 | PX SEND BROADCAST | :TQ10000 | 0 | 0 | 00:00:00.01 | 0 | |
8 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | DEPT_PARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 |
10 | PX BLOCK ITERATOR | 0 | 1087K | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | EMP_PARTITION2 | 0 | 1087K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
9 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
filter("E"."DEPTNO"="D"."DEPTNO")
SQL> BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(USER,
3 'EMP_PARTITION2',
4 CASCADE => TRUE);
5 END;
6 /
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:14.14 | 20 | |
2 | PX COORDINATOR | 1 | 2 | 00:00:14.04 | 20 | ||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 |
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |
5 | NESTED LOOPS | 0 | 3999K | 0 | 00:00:00.01 | 0 | |
6 | PX RECEIVE | 0 | 0 | 00:00:00.01 | 0 | ||
7 | PX SEND BROADCAST | :TQ10000 | 0 | 0 | 00:00:00.01 | 0 | |
8 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | DEPT_PARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 |
10 | PX BLOCK ITERATOR | 0 | 999K | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | EMP_PARTITION2 | 0 | 999K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
9 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
filter("E"."DEPTNO"="D"."DEPTNO")
SQL> BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(USER,
3 'DEPT_PARTITION',
4 CASCADE => TRUE);
5 END;
6 /
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:00.81 | 20 | |
2 | PX COORDINATOR | 1 | 2 | 00:00:00.71 | 20 | ||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 |
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |
5 | NESTED LOOPS | 0 | 3999K | 0 | 00:00:00.01 | 0 | |
6 | PX RECEIVE | 0 | 0 | 00:00:00.01 | 0 | ||
7 | PX SEND BROADCAST | :TQ10000 | 0 | 0 | 00:00:00.01 | 0 | |
8 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | DEPT_PARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 |
10 | PX BLOCK ITERATOR | 0 | 999K | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | EMP_PARTITION2 | 0 | 999K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
9 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
filter("E"."DEPTNO"="D"."DEPTNO")
{CODE} | 머야 왜안되는거야??? =_= ( HASH PARTITION... ) |
---|---|
{CODE:SQL} QL> CREATE TABLE EMP_PARTITION3( 2 EMPNO NUMBER NOT NULL, 3 ENAME VARCHAR2(10), 4 JOB VARCHAR2(9), 5 MGR NUMBER(4), 6 HIREDATE DATE, 7 SAL NUMBER(7, 2), 8 COMM NUMBER(7, 2), 9 DEPTNO NUMBER(2)) 10 PARTITION BY HASH (DEPTNO) ( 11 PARTITION P1 12 , PARTITION P2 13 , PARTITION P3 14 , PARTITION P4 15 ); |
테이블이 생성되었습니다.
QL> CREATE TABLE DEPT_PARTITION3(
2 DEPTNO NUMBER(2)
3 , DNAME VARCHAR2(14)
4 , LOC VARCHAR2(13))
5 PARTITION BY HASH ( DEPTNO) (
6 PARTITION P1
7 , PARTITION P2
8 , PARTITION P3
9 , PARTITION P4
10 );
테이블이 생성되었습니다.
SQL> INSERT INTO EMP_PARTITION3
2 SELECT LEVEL AS EMPNO
3 , 'TEST' AS ENAME
4 , 'JOB' AS JOB
5 , 1000 AS MGR
6 , SYSDATE AS HIREDATE
7 , FLOOR( DBMS_RANDOM.VALUE ) * 10000 AS SAL
8 , FLOOR( DBMS_RANDOM.VALUE ) * 100 AS COMM
9 , MOD( LEVEL, 4 ) * 10 + 10 AS DEPTNO
10 FROM DUAL
11 CONNECT BY LEVEL <= 4000000;
4000000 개의 행이 만들어졌습니다.
SQL> COMMIT;
SQL> INSERT INTO DEPT_PARTITION3 VALUES (10, 'ACCOUNTING', 'NEW YORK');
SQL> INSERT INTO DEPT_PARTITION3 VALUES (20, 'RESEARCH', 'DALLAS');
SQL> INSERT INTO DEPT_PARTITION3 VALUES (30, 'SALES', 'CHICAGO');
SQL> INSERT INTO DEPT_PARTITION3 VALUES (40, 'OPERATIONS', 'BOSTON');
SQL> COMMIT;
SQL> SELECT /*+ gather_plan_statistics FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) /COUNT()
2 FROM DEPT_PARTITION3 D, EMP_PARTITION3 E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:05.74 | 19 | |
2 | PX COORDINATOR | 1 | 2 | 00:00:05.64 | 19 | ||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 |
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |
5 | NESTED LOOPS | 0 | 5195K | 0 | 00:00:00.01 | 0 | |
6 | PX RECEIVE | 0 | 0 | 00:00:00.01 | 0 | ||
7 | PX SEND BROADCAST | :TQ10000 | 0 | 0 | 00:00:00.01 | 0 | |
8 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | DEPT_PARTITION3 | 0 | 4 | 0 | 00:00:00.01 | 0 |
10 | PX BLOCK ITERATOR | 0 | 1298K | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | EMP_PARTITION3 | 0 | 1298K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
9 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
filter("E"."DEPTNO"="D"."DEPTNO")
SQL> BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(USER,
3 'DEPT_PARTITION3',
4 CASCADE => TRUE);
5 END;
6 /
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> BEGIN
2 DBMS_STATS.GATHER_TABLE_STATS(USER,
3 'EMP_PARTITION3',
4 CASCADE => TRUE);
5 END;
6 /
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> SELECT /*+ gather_plan_statistics FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) /COUNT()
2 FROM DEPT_PARTITION3 D, EMP_PARTITION3 E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:09.22 | 19 | |
2 | PX COORDINATOR | 1 | 2 | 00:00:09.22 | 19 | ||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 |
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |
5 | NESTED LOOPS | 0 | 4003K | 0 | 00:00:00.01 | 0 | |
6 | PX RECEIVE | 0 | 0 | 00:00:00.01 | 0 | ||
7 | PX SEND BROADCAST | :TQ10000 | 0 | 0 | 00:00:00.01 | 0 | |
8 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | DEPT_PARTITION3 | 0 | 4 | 0 | 00:00:00.01 | 0 |
10 | PX BLOCK ITERATOR | 0 | 1000K | 0 | 00:00:00.01 | 0 | |
| TABLE ACCESS FULL | EMP_PARTITION3 | 0 | 1000K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
9 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
filter("E"."DEPTNO"="D"."DEPTNO")
SQL> SELECT /*+ gather_plan_statistics USE_HASH( D E ) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) /COUNT()
2 FROM DEPT_PARTITION3 D, EMP_PARTITION3 E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:02.75 | 19 | ||||
2 | PX COORDINATOR | 1 | 2 | 00:00:02.65 | 19 | |||||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 | |||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
| HASH JOIN | 0 | 4003K | 0 | 00:00:00.01 | 0 | 1517K | 1517K | 753K (0) | |
6 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | DEPT_PARTITION3 | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
8 | PX RECEIVE | 0 | 4003K | 0 | 00:00:00.01 | 0 | ||||
9 | PX SEND BROADCAST LOCAL | :TQ10000 | 0 | 4003K | 0 | 00:00:00.01 | 0 | |||
10 | PX BLOCK ITERATOR | 0 | 4003K | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | EMP_PARTITION3 | 0 | 4003K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
5 - access("E"."DEPTNO"="D"."DEPTNO")
7 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
SQL> SELECT /*+ gather_plan_statistics USE_HASH( D E ) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(D, NONE, NONE) /COUNT()
2 FROM DEPT_PARTITION3 D, EMP_PARTITION3 E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:00.74 | 19 | ||||
2 | PX COORDINATOR | 1 | 2 | 00:00:00.64 | 19 | |||||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 | |||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
| HASH JOIN | 0 | 4003K | 0 | 00:00:00.01 | 0 | 1517K | 1517K | 753K (0) | |
6 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | DEPT_PARTITION3 | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
8 | PX RECEIVE | 0 | 4003K | 0 | 00:00:00.01 | 0 | ||||
9 | PX SEND BROADCAST LOCAL | :TQ10000 | 0 | 4003K | 0 | 00:00:00.01 | 0 | |||
10 | PX BLOCK ITERATOR | 0 | 4003K | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | EMP_PARTITION3 | 0 | 4003K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
5 - access("E"."DEPTNO"="D"."DEPTNO")
7 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
--RANGE PARTITION
SQL> SELECT DEPTNO, COUNT( * ) FROM EMP_PARTITION2
2 GROUP BY DEPTNO;
DEPTNO COUNT(*)
DEPTNO COUNT(*)
{CODE} |
힌트 | ||
---|---|---|
{CODE:SQL} /*+ PQ_DISTRIBUTE( Inner 테이블 명, outer_distribution, inner_distribution ) */ | {CODE} | 실행 + _ + |
{CODE:SQL} SQL> SELECT /*+ gather_plan_statistics LEADING(E D) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(D, NONE, NONE) /COUNT() 2 FROM DEPT_PARTITION3 D, EMP_PARTITION3 E 3 WHERE E.DEPTNO = D.DEPTNO; |
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:01.34 | 19 | |||||
2 | PX COORDINATOR | 1 | 2 | 00:00:01.34 | 19 | ||||||
3 | PX SEND QC (RANDOM) | :TQ10000 | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |||||
5 | PX PARTITION HASH ALL | 0 | 4003K | 0 | 00:00:00.01 | 0 | |||||
| HASH JOIN | 0 | 4003K | 0 | 00:00:00.01 | 0 | 184M | 15M | 10M (1) | 11264 | |
7 | TABLE ACCESS FULL | EMP_PARTITION3 | 0 | 4003K | 0 | 00:00:00.01 | 0 | ||||
8 | TABLE ACCESS FULL | DEPT_PARTITION3 | 0 | 4 | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
6 - access("E"."DEPTNO"="D"."DEPTNO")
SQL> SELECT /*+ gather_plan_statistics LEADING(E D) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(D, NONE, NONE) /COUNT()
2 FROM DEPT_PARTITION D, EMP_PARTITION2 E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:01.45 | 20 | |||||
2 | PX COORDINATOR | 1 | 2 | 00:00:01.35 | 20 | ||||||
3 | PX SEND QC (RANDOM) | :TQ10000 | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |||||
5 | PX PARTITION RANGE ALL | 0 | 3999K | 0 | 00:00:00.01 | 0 | |||||
| HASH JOIN | 0 | 3999K | 0 | 00:00:00.01 | 0 | 92M | 8084K | 3743K (1) | 11264 | |
7 | TABLE ACCESS FULL | EMP_PARTITION2 | 0 | 3999K | 0 | 00:00:00.01 | 0 | ||||
8 | TABLE ACCESS FULL | DEPT_PARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
6 - access("E"."DEPTNO"="D"."DEPTNO")
{CODE} * 우선 설명... * 그림 7-7 참조 ( P 707 ) * 조인 컬럼으로 파티션이 생성 되어야 한다. * 병렬도가 2라고 가정하면 그림 7-7에서 보듯 P000과 P001서버 프로세스가 각각 Partition Pair 1과 Partition Pair 2를 처리한다. * 조인을 수행하는동안 P000과 P001 프로세스가 데이터를 주고 받으며 통신할 필요가 전혀 없다. 이미 상호 배타적인 Partition-Pair가 형성돼 있으돼 있으므로 각 서버 프로세스가 하나씩 독립적으로 조인을 수행할 수 있는 것이다. * 참고로, P000, P001 서버 프로세스 옆에 표시된 숫자 9와 5는 각각 조인을 수행하고 나서 QC에게 전송한 조인 결과 건수다. * 만약 Partition-Pair가 10개면, 두 개 서버 프로세스가 각각 5개씩 순차적으로 처리하면 된다. * Hash join 바로 위쪽에 'PX PARTITION RANGE ALL' OR 'PX PARTITION RANGE ITERATOR'라고 표시되는 것을 통해 Full Partition Wise 조인인 것을 확인 할 수 있다. * 다른 병렬 조인은 두개의 서버집합이 필요한 반면, 여기서 하나의 서버집합만 필요함 * Full Partition Wise 조인은 파티션 기반 Granule이므로 서버 프로세스 개수는 파티션 개수이하로 제한된다. * 파티션 방식은 어떤 것이든 상관없다 단지 조인 컬럼의 같은 방식 및 기준으로 파티셔닝 돼 있다면 서로 방해받지 않고 Partition pair끼리 독립적인 조인이 가능하기 때문이다. |
PQ_DISTRIBUTE(D, BROADCAST, NONE) 기존에 이렇게 옵티마이져가 판단한거임 |
---|
{CODE:SQL} |
SQL> SELECT /*+ gather_plan_statistics LEADING(E D) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(D, BROADCAST, NONE) /COUNT()
2 FROM DEPT_PARTITION D, EMP_PARTITION2 E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:16.10 | 20 | |||||
2 | PX COORDINATOR | 1 | 2 | 00:00:16.00 | 20 | ||||||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |||||
| HASH JOIN | 0 | 3999K | 0 | 00:00:00.01 | 0 | 184M | 15M | 3499K (1) | 40960 | |
6 | PX RECEIVE | 0 | 3999K | 0 | 00:00:00.01 | 0 | |||||
7 | PX SEND BROADCAST | :TQ10000 | 0 | 3999K | 0 | 00:00:00.01 | 0 | ||||
8 | PX BLOCK ITERATOR | 0 | 3999K | 0 | 00:00:00.01 | 0 | |||||
| TABLE ACCESS FULL | EMP_PARTITION2 | 0 | 3999K | 0 | 00:00:00.01 | 0 | ||||
10 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | |||||
| TABLE ACCESS FULL | DEPT_PARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
5 - access("E"."DEPTNO"="D"."DEPTNO")
9 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
{CODE} |
참조 : http://scidb.tistory.com/tag/PQ_DISTRIBUTE ( 시간 남으면 테스트해볼게요 ... 아마 못할듯 =_= )
준비 스크립트 |
---|
{CODE:SQL} SQL> CREATE TABLE DEPT_NOPARTITION( 2 DEPTNO NUMBER(2) 3 , DNAME VARCHAR2(14) 4 , LOC VARCHAR2(13) 5 ); |
테이블이 생성되었습니다.
SQL> INSERT INTO DEPT_NOPARTITION VALUES (10, 'ACCOUNTING', 'NEW YORK');
SQL> INSERT INTO DEPT_NOPARTITION VALUES (20, 'RESEARCH', 'DALLAS');
SQL> INSERT INTO DEPT_NOPARTITION VALUES (30, 'SALES', 'CHICAGO');
SQL> INSERT INTO DEPT_NOPARTITION VALUES (40, 'OPERATIONS', 'BOSTON');
SQL> COMMIT;
SQL> begin dbms_stats.gather_table_stats( user , 'DEPT_NOPARTITION' , cascade => true ); end;
2 /
PL/SQL 처리가 정상적으로 완료되었습니다.
SQL> SELECT /*+ gather_plan_statistics LEADING( E D ) USE_HASH( E ) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(E, NONE , BROADCAST) /COUNT()
2 FROM DEPT_NOPARTITION D, EMP_PARTITION3 E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
--어라?
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:01.88 | 13 | |||||
2 | PX COORDINATOR | 1 | 2 | 00:00:01.78 | 13 | ||||||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |||||
| HASH JOIN | 0 | 4003K | 0 | 00:00:00.01 | 0 | 70M | 8084K | 8328K (1) | 31744 | |
6 | PX PARTITION HASH ALL | 0 | 4003K | 0 | 00:00:00.01 | 0 | |||||
7 | TABLE ACCESS FULL | EMP_PARTITION3 | 0 | 4003K | 0 | 00:00:00.01 | 0 | ||||
8 | PX RECEIVE | 0 | 4 | 0 | 00:00:00.01 | 0 | |||||
9 | PX SEND PARTITION (KEY) | :TQ10000 | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
10 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | |||||
| TABLE ACCESS FULL | DEPT_NOPARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
5 - access("E"."DEPTNO"="D"."DEPTNO")
11 - access(:Z>=:Z AND :Z<=:Z)
SQL> SELECT /*+ gather_plan_statistics LEADING( D E ) USE_HASH( E ) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(E,BROADCAST, NONE ) /COUNT()
2 FROM DEPT_NOPARTITION D, EMP_PARTITION3 E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:00.90 | 13 | ||||
2 | PX COORDINATOR | 1 | 2 | 00:00:00.80 | 13 | |||||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 | |||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
| HASH JOIN | 0 | 4003K | 0 | 00:00:00.01 | 0 | 1517K | 1517K | 990K (0) | |
6 | PX RECEIVE | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
7 | PX SEND BROADCAST | :TQ10000 | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
8 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | DEPT_NOPARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
10 | PX BLOCK ITERATOR | 0 | 4003K | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | EMP_PARTITION3 | 0 | 4003K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
5 - access("E"."DEPTNO"="D"."DEPTNO")
9 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
SQL> SELECT /*+ gather_plan_statistics LEADING( D ) USE_HASH( E ) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(E,PARTITION, NONE ) /COUNT()
2 FROM DEPT_NOPARTITION D, EMP_PARTITION3 E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:01.25 | 13 | ||||
2 | PX COORDINATOR | 1 | 2 | 00:00:01.15 | 13 | |||||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 | |||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
| HASH JOIN | 0 | 4003K | 0 | 00:00:00.01 | 0 | 1517K | 1517K | 869K (0) | |
6 | PX RECEIVE | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
7 | PX SEND PARTITION (KEY) | :TQ10000 | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
8 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | DEPT_NOPARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
10 | PX PARTITION HASH ALL | 0 | 4003K | 0 | 00:00:00.01 | 0 | ||||
11 | TABLE ACCESS FULL | EMP_PARTITION3 | 0 | 4003K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
5 - access("E"."DEPTNO"="D"."DEPTNO")
9 - access(:Z>=:Z AND :Z<=:Z)
{CODE} * 그림 7 - 8 ( p709 ) * Full Partition Wise 전에 하나의 순서 서버 집합을 만들어서 실행한다. * dept 테이블이 emp 테이블 기준으로 파티셔닝 되는 것을 표현 하고 있다. |
준비 스크립트 |
---|
{CODE:SQL} |
SQL> CREATE TABLE EMP_NOPARTITION(
2 EMPNO NUMBER NOT NULL,
3 ENAME VARCHAR2(10),
4 JOB VARCHAR2(9),
5 MGR NUMBER(4),
6 HIREDATE DATE,
7 SAL NUMBER(7, 2),
8 COMM NUMBER(7, 2),
9 DEPTNO NUMBER(2)
10 );
테이블이 생성되었습니다.
SQL> INSERT INTO EMP_NOPARTITION
2 SELECT LEVEL AS EMPNO
3 , 'TEST' AS ENAME
4 , 'JOB' AS JOB
5 , 1000 AS MGR
6 , SYSDATE AS HIREDATE
7 , FLOOR( DBMS_RANDOM.VALUE ) * 10000 AS SAL
8 , FLOOR( DBMS_RANDOM.VALUE ) * 100 AS COMM
9 , MOD( LEVEL, 4 ) * 10 + 10 AS DEPTNO
10 FROM DUAL
11 CONNECT BY LEVEL <= 4000000;
4000000 개의 행이 만들어졌습니다.
SQL> COMMIT
{CODE} | 동적으로 파티셔닝 하는 방식( 테이타 분포도가 같을 경우, HASH, HASH ) |
---|---|
{CODE:SQL} |
SQL> SELECT /*+ gather_plan_statistics LEADING( D ) USE_HASH( E ) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(E,HASH, HASH ) /COUNT()
2 FROM DEPT_NOPARTITION D, EMP_NOPARTITION E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:01.03 | 8 | ||||
2 | PX COORDINATOR | 1 | 2 | 00:00:01.03 | 8 | |||||
3 | PX SEND QC (RANDOM) | :TQ10002 | 0 | 1 | 0 | 00:00:00.01 | 0 | |||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
| HASH JOIN | 0 | 2888K | 0 | 00:00:00.01 | 0 | 1517K | 1517K | 874K (0) | |
6 | PX RECEIVE | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
7 | PX SEND HASH | :TQ10000 | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
8 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | DEPT_NOPARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
10 | PX RECEIVE | 0 | 2888K | 0 | 00:00:00.01 | 0 | ||||
11 | PX SEND HASH | :TQ10001 | 0 | 2888K | 0 | 00:00:00.01 | 0 | |||
12 | PX BLOCK ITERATOR | 0 | 2888K | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | EMP_NOPARTITION | 0 | 2888K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
5 - access("E"."DEPTNO"="D"."DEPTNO")
9 - access(:Z>=:Z AND :Z<=:Z)
13 - access(:Z>=:Z AND :Z<=:Z)
{CODE} * 그림 7-9 * 1단계 : 첫 번째 서버 집합이 DEPT테이블을 읽어 두 번째 서버 집합에 전송한다.( 상호 배타적 ) * 2단계 : 첫 번째 서버 집합이 EMP테이블 읽어 두 번째 서버 집합에 전송한다.( 상호 배타적 이 아닌것 같다...=_= ) * 첫 번째 서버 집합은 데이터를 분배하는 역할을 하고, 두 번째 서버 집합은 받은 데이터를 파티셔닝하는 역할을 한다. 가능하면 메모리 내에서 파티셔닝 하겠지만 공간이 부족할 때는 Temp 테이블 스페이스를 활용 할 것이다. * 이렇게 2단계까지 완료 하고 나면 이제 Partition-Pair가 구성되었으므로 Full Partition Wise 조인을 수핼 할 수 있게 되었다. |
그림 7-10( p712 )
쓸때 없이 노는 프로세스 현상 쿼리 |
---|
{CODE:SQL} |
SELECT /*+ FULL( A ) FULL( B ) PARALLEL( A 16 ) PARALLEL( B 16 ) */ A.사원명, ....
FROM 사원 A, 상품권 B
WHERE A.사번 = B.담당자사번
{CODE} * 물론 이런 특징은 Partition Wise 조인에서도 똑같이 볼수 있지만 병렬 조인의 비효율 보다는 파티션 전략의 오류로 보는 게 타당하다. * 동적으로 파티셔닝 해야 하는 상황에서는 위와 같은 현상이 실제 일어 날 수있기 때문에 조인문을 작성 할 때 세심한 주의가 필요하다. 사원 테이블이 매우 적다면 이어서 설명할 BroadCast 방식을 사용함으로써 쉽게 문제를 해결 할 수 있지만 그렇지 않다면 아예 병렬 조인을 포기하는것이 나을 수도 있다. |
동적으로 파티셔닝 하는 방식 ( 아웃터 테이블의 테이타가 적을경우 힌트 ) |
---|
{CODE:SQL} |
SQL> SELECT /*+ gather_plan_statistics LEADING( D E ) USE_HASH( E ) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(E,BroadCast, None ) /COUNT()
2 FROM DEPT_NOPARTITION D, EMP_NOPARTITION E
3 WHERE E.DEPTNO = D.DEPTNO;
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:00.89 | 8 | ||||
2 | PX COORDINATOR | 1 | 2 | 00:00:00.80 | 8 | |||||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 | |||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
| HASH JOIN | 0 | 2888K | 0 | 00:00:00.01 | 0 | 1517K | 1517K | 993K (0) | |
6 | PX RECEIVE | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
7 | PX SEND BROADCAST | :TQ10000 | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
8 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | DEPT_NOPARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 | |||
10 | PX BLOCK ITERATOR | 0 | 2888K | 0 | 00:00:00.01 | 0 | ||||
| TABLE ACCESS FULL | EMP_NOPARTITION | 0 | 2888K | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
5 - access("E"."DEPTNO"="D"."DEPTNO")
9 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
{CODE} | 동적으로 파티셔닝 하는 방식 ( 아웃터 테이블의 테이타가 적을경우 힌트을 걸어야 하는데 반대로 걸었을 경우.. IO부하가 심하다구..?? =_=) |
---|---|
{CODE:SQL} SQL> SELECT /*+ gather_plan_statistics LEADING( E ) USE_HASH( E ) FULL( D ) FULL ( E ) PARALLEL(D 2) PARALLEL(E 2) PQ_DISTRIBUTE(D ,BroadCast, NONE ) /COUNT() 2 FROM DEPT_NOPARTITION D, EMP_NOPARTITION E 3 WHERE E.DEPTNO = D.DEPTNO; |
COUNT(*)
SQL> @XPLAN
Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | Used-Tmp |
1 | SORT AGGREGATE | 1 | 1 | 1 | 00:00:05.69 | 8 | |||||
2 | PX COORDINATOR | 1 | 2 | 00:00:05.69 | 8 | ||||||
3 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 1 | 0 | 00:00:00.01 | 0 | ||||
4 | SORT AGGREGATE | 0 | 1 | 0 | 00:00:00.01 | 0 | |||||
| HASH JOIN | 0 | 2888K | 0 | 00:00:00.01 | 0 | 184M | 15M | 3096K (1) | 40960 | |
6 | PX RECEIVE | 0 | 2888K | 0 | 00:00:00.01 | 0 | |||||
7 | PX SEND BROADCAST | :TQ10000 | 0 | 2888K | 0 | 00:00:00.01 | 0 | ||||
8 | PX BLOCK ITERATOR | 0 | 2888K | 0 | 00:00:00.01 | 0 | |||||
| TABLE ACCESS FULL | EMP_NOPARTITION | 0 | 2888K | 0 | 00:00:00.01 | 0 | ||||
10 | PX BLOCK ITERATOR | 0 | 4 | 0 | 00:00:00.01 | 0 | |||||
| TABLE ACCESS FULL | DEPT_NOPARTITION | 0 | 4 | 0 | 00:00:00.01 | 0 |
Predicate Information (identified by operation id):
5 - access("E"."DEPTNO"="D"."DEPTNO")
9 - access(:Z>=:Z AND :Z<=:Z)
11 - access(:Z>=:Z AND :Z<=:Z)
{CODE} * 그림 7 - 11 ( p715 ) * 조인 컬럼에 대해 어느 한 쪽도 파티션이 되지 않은 상황에서 오라클이 선택할 수 있는 두 번째 방식은 BroadCast 방식으로, 두 테이블 중 작은 쪽을 반대편 서버 집합의 "모든" 프로세스에 Broadcast하고 나서 조인을 수행하는 방식이다. * 1단계 : 첫 번째 서버 집합에 속한 프로세스들이 각자 읽은 dept 테이블 레코드를 두 번째 서버 집합에 속한 모든 병렬 프로세스에게 전송한다. * 2단계 : 두 번째 서버 집합에 속한 프로세스들이 각자 맡은 범위와 emp 테이블을 읽으면서 병렬로 조인을 수행한다. 1단계가 완료되면 두 번째 서버 집합에 속한 프로세스 모두 dept 테이블의 완전한 집합을 갖게 되므로 프로세스 간 상호 간섭 없이 독립적으로 조인 수행이 가능하다. * 양쪽 테이블 모두 파티션 되지 않았을 때는 1차적으로 BroadCast 방식이 고려되어야 한다. 양쪽 테이블을 동적으로 파티셔닝 하는 방식은 앞서 설명한 것처럼 메모리 자원과 Temp 테이블 스페이스 공간을 많이 사용하는 반면 이 방식은 리소스 사용량이 매우 적기 때문이다. BroadCast 되는 테이블이 아주 작을 때만 적용된다. 만약 Broadcast되는 테이블이 중대형 이상일 때는 과도한 프로세스 간 통신 때문에 성능이 매우 느려질 수 있다. 또한 두 번재 서버 집합이 메모리 내에서는 감당하기 어려울 정도로 큰 테이블을 BroadCast한다면 Temp 테이블스페이스 공간을 사용하게 되면서 그 성능 저하는 심각하게 저하될 것이다. 따라서 한쪽 테이블이 충분히 작을 때만 유용하다. |
병렬 조인 방식 | 특징 |
---|---|
Full Partition Wise 조인 | ☞ 두 테이블 모두 조인 컬럼에 대해 같은 기준으로 파티셔닝 ☞ 데이터 재분배 불필요 -> 단일 서버 집합만으로 수정 |
Partial Partition Wise 조인 | ☞ 둘 중 한 테이블만 조인 컬럼에 대해 파티셔닝된 경우 ☞ 파티셔닝되지 않은 다른 쪽 테이블을 같은 기준으로 파티셔닝하고 나서 Full Partition Wise 조인 ☞ 동적 파티셔닝을 위한 데이터 재분배 필요 -> 두 개의 서버 집합이 작업 수행 |
동적 파티셔닝 | ☞ 어느 한 쪽도 조인 컬럼에 대해 파티셔닝되지 않은 상황 ☞ 양쪽 테이블이 모두 대용량 ☞ 임시 테이블스페이스를 많이 사용 ☞ 양쪽 테이블 모두 전체범위처리 ☞ 조인 컬럼의 데이터 분포가 균일해야 함 |
Broadcase | ☞ 어느 한 쪽도 조인 컬럼에 대해 파티셔닝되지 않은 상황 ☞ 둘 중 하나의 테이블이 매우 적을 때 ☞ 동적 파티셔닝이 불필요 -> 큰 테이블에 대한 부분범위처리 가능 |