oracle에서 not exists 대외 검색의 영향 설명

11517 단어
전언
최근 동료가 발견한 문제는 12c에서 뛰는 버퍼 get은 높지만 10g에서 뛰는 버퍼는 낮다는 것이다.12c의 최적화기에 문제가 있는지 의심스럽다.
이 10g의 환경과 12c의 환경은 데이터 양이 대체적으로 같고 아주 적은 부분만 다르다. 그러나 이 아주 적은 부분이 다르기 때문에 not exists의 하위 조회가 서로 다른 값을 되돌려주고 외부 조회에 서로 다른 영향을 미친다.
우리는 다음과 같은 코드로 시뮬레이션을 해 봅시다.
초기화 데이터:

--10g
drop table t1;
drop table t2;
 
create table t1 (id number,name varchar2(20),dep_id varchar2(10));
create table t2 (id number,name varchar2(20),dep_id varchar2(10));
 
insert into t1 select rownum,'a','kk' from dual connect by level <=3000000;
insert into t2 select rownum,'a','kk' from dual connect by level <=1000000;
insert into t2 select rownum,'a','mm' from dual;
 
commit;
 
 
--12c
drop table t1;
drop table t2;
 
create table t1 (id number,name varchar2(20),dep_id varchar2(10));
create table t2 (id number,name varchar2(20),dep_id varchar2(10));
 
 
insert into t1 select rownum,'a','kk' from dual connect by level <=3000000;
insert into t2 select rownum,'a','kk' from dual connect by level <=1000000;
 
commit;

우리가 보기에 12c의 데이터와 10g은 아주 적은 차이일 뿐이다. t1표 12c와 10g은 모두 같고 t2표는 12c에서 한 줄의 데이터만 적다.

--10g
SQL> select dep_id,count(*) from t1 group by dep_id;
 
DEP_ID     COUNT(*)
-------------------- ----------
kk      3000000
 
SQL> select dep_id,count(*) from t2 group by dep_id;
 
DEP_ID     COUNT(*)
-------------------- ----------
mm       1
kk      1000000
 
SQL>
 
 
--12c
SQL> select dep_id,count(*) from t1 group by dep_id;
 
DEP_ID     COUNT(*)
-------------------- ----------
kk      3000000
 
SQL> select dep_id,count(*) from t2 group by dep_id;
 
DEP_ID     COUNT(*)
-------------------- ----------
kk      1000000
 
SQL>

우리가 실행할 sql 문장은 다음과 같습니다.

select count(*)
 from t1, t2
 where t1.id = t2.id
 and t1.dep_id = 'kk'
 and not exists (select 1
   from t1, t2
   where t1.id = t2.id
   and t2.dep_id = 'mm');

우선 실행 상황의 차이를 살펴보자. 10g의 버퍼링은 작고 12c가 많다.

--10g
SQL> select /*+ gather_plan_statistics */ count(*) from t1,t2 where t1.id=t2.id and t1.dep_id='kk' and not exists (select 1 from t1,t2 where t1.id=t2.id and t2.dep_id='mm');
 
 COUNT(*)
----------
   0
 
SQL> select* from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
 
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 22t5mb43w55pr, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from t1,t2 where t1.id=t2.id and t1.dep_id='kk' and not
exists (select 1 from t1,t2 where t1.id=t2.id and t2.dep_id='mm')
 
Plan hash value: 3404612428
 
------------------------------------------------------------------------------------------------------------------
| Id | Operation   | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT  |  |  1 |  |  1 |00:00:00.02 | 2086 |  |  |   |
| 1 | SORT AGGREGATE  |  |  1 |  1 |  1 |00:00:00.02 | 2086 |  |  |   |
|* 2 | FILTER    |  |  1 |  |  0 |00:00:00.02 | 2086 |  |  |   |
|* 3 | HASH JOIN   |  |  0 | 901K|  0 |00:00:00.01 |  0 | 39M| 5518K|   |
| 4 |  TABLE ACCESS FULL| T2 |  0 | 901K|  0 |00:00:00.01 |  0 |  |  |   |
|* 5 |  TABLE ACCESS FULL| T1 |  0 | 2555K|  0 |00:00:00.01 |  0 |  |  |   |
|* 6 | HASH JOIN   |  |  1 |  23 |  1 |00:00:00.02 | 2086 | 1517K| 1517K| 612K (0)|
|* 7 |  TABLE ACCESS FULL| T2 |  1 |  23 |  1 |00:00:00.02 | 2082 |  |  |   |
| 8 |  TABLE ACCESS FULL| T1 |  1 | 2555K|  1 |00:00:00.01 |  4 |  |  |   |
------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
 2 - filter( IS NULL)
 3 - access("T1"."ID"="T2"."ID")
 5 - filter("T1"."DEP_ID"='kk')
 6 - access("T1"."ID"="T2"."ID")
 7 - filter("T2"."DEP_ID"='mm')
 
Note
-----
 - dynamic sampling used for this statement
 
 
34 rows selected.
 
SQL>
 
 
--12c
SQL> select /*+ gather_plan_statistics */ count(*) from t1,t2 where t1.id=t2.id and t1.dep_id='kk' and not exists (select 1 from t1,t2 where t1.id=t2.id and t2.dep_id='mm');
 
 COUNT(*)
----------
 1000000
 
SQL> select* from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
 
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 22t5mb43w55pr, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from t1,t2 where
t1.id=t2.id and t1.dep_id='kk' and not exists (select 1 from t1,t2
where t1.id=t2.id and t2.dep_id='mm')
 
Plan hash value: 1692274438
 
--------------------------------------------------------------------------------------------------------------------
| Id | Operation    | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT  |  |  1 |  |  1 |00:00:00.79 | 10662 |  | |  |
| 1 | SORT AGGREGATE  |  |  1 |  1 |  1 |00:00:00.79 | 10662 |  | |  |
|* 2 | FILTER    |  |  1 |  | 1000K|00:00:00.74 | 10662 |  | |  |
|* 3 | HASH JOIN   |  |  1 | 1215K| 1000K|00:00:00.52 | 8579 | 43M| 6111K| 42M (0)|
| 4 |  TABLE ACCESS FULL | T2 |  1 | 1215K| 1000K|00:00:00.01 | 2083 |  | |  |
|* 5 |  TABLE ACCESS FULL | T1 |  1 | 2738K| 3000K|00:00:00.07 | 6496 |  | |  |
|* 6 | HASH JOIN RIGHT SEMI|  |  1 |  35 |  0 |00:00:00.02 | 2083 | 1245K| 1245K| 461K (0)|
|* 7 |  TABLE ACCESS FULL | T2 |  1 |  23 |  0 |00:00:00.02 | 2083 |  | |  |
| 8 |  TABLE ACCESS FULL | T1 |  0 | 2738K|  0 |00:00:00.01 |  0 |  | |  |
--------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
 2 - filter( IS NULL)
 3 - access("T1"."ID"="T2"."ID")
 5 - filter("T1"."DEP_ID"='kk')
 6 - access("T1"."ID"="T2"."ID")
 7 - filter("T2"."DEP_ID"='mm')
 
Note
-----
 - dynamic statistics used: dynamic sampling (level=2)
 
 
35 rows selected.
 
SQL>
SQL>

23, 24줄을 볼 수 있는데 10g에서 실행할 때 버퍼스는 0이고 12c에서 78, 79줄에서 버퍼는 2083+6496이다.
즉, 10g에서 외계 조회는 t1과 t2의 스캐닝을 하지 않고 결과를 직접 되돌려주고, 12c에서 외계 조회는 t1표와 t2 표층 스캐닝을 해야 결과를 되돌려준다.
이것은 사실 10g과 12c의 차이가 아니라 not exists의 반환 데이터가 외부에 미치는 영향이다.하위 쿼리는 0줄 기록을 되돌려야 not exist의 조건을 충족시켜 외부 쿼리 결과를 되돌려줍니다.
10g에서 하위 조회가 한 줄의 기록을 되돌려주었다

--10g
SQL> select 1 from t1,t2 where t1.id=t2.id and t2.dep_id='mm';
 
   1
----------
   1
 
SQL>

not exists(즉 0줄이 만족해야 함)가 충족되지 않기 때문에 외부에서 계속 조회할 필요가 없습니다.기록 0 줄을 직접 되돌려줍니다.
12c에서 하위 조회는 0줄 기록을 되돌려주고 not exist의 조건을 충족시키기 때문에 외부 조회에서 계속 조회해야 한다.

--12c
SQL> select count(*) from t1,t2 where t1.id=t2.id and t2.dep_id='kk';
 
 COUNT(*)
----------
 1000000
 
SQL> set line 1000
SQL> set pages 1000
SQL> col PLAN_TABLE_OUTPUT for a250
SQL>
SQL>
SQL> select /*+ gather_plan_statistics */ count(*) from t1,t2 where t1.id=t2.id and t1.dep_id='kk' and not exists (select 1 from t1,t2 where t1.id=t2.id and t2.dep_id='kk');
 
 COUNT(*)
----------
   0
 
SQL> select* from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
 
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID c5hj2p2jt1fxf, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from t1,t2 where
t1.id=t2.id and t1.dep_id='kk' and not exists (select 1 from t1,t2
where t1.id=t2.id and t2.dep_id='kk')
 
Plan hash value: 1692274438
 
--------------------------------------------------------------------------------------------------------------------
| Id | Operation    | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT  |  |  1 |  |  1 |00:00:00.28 | 2087 |  | |  |
| 1 | SORT AGGREGATE  |  |  1 |  1 |  1 |00:00:00.28 | 2087 |  | |  |
|* 2 | FILTER    |  |  1 |  |  0 |00:00:00.28 | 2087 |  | |  |
|* 3 | HASH JOIN   |  |  0 | 1215K|  0 |00:00:00.01 |  0 | 69M| 7428K|   |
| 4 |  TABLE ACCESS FULL | T2 |  0 | 1215K|  0 |00:00:00.01 |  0 |  | |  |
|* 5 |  TABLE ACCESS FULL | T1 |  0 | 2738K|  0 |00:00:00.01 |  0 |  | |  |
|* 6 | HASH JOIN RIGHT SEMI|  |  1 | 2738K|  1 |00:00:00.28 | 2087 | 43M| 6111K| 42M (0)|
|* 7 |  TABLE ACCESS FULL | T2 |  1 | 1215K| 1000K|00:00:00.12 | 2083 |  | |  |
| 8 |  TABLE ACCESS FULL | T1 |  1 | 2738K|  1 |00:00:00.01 |  4 |  | |  |
--------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
 2 - filter( IS NULL)
 3 - access("T1"."ID"="T2"."ID")
 5 - filter("T1"."DEP_ID"='kk')
 6 - access("T1"."ID"="T2"."ID")
 7 - filter("T2"."DEP_ID"='kk')
 
Note
-----
 - dynamic statistics used: dynamic sampling (level=2)
 
 
35 rows selected.
 
SQL>

38, 39 줄의 버퍼가 0인 것을 볼 수 있습니다.
총결산
이상은 이 글의 전체 내용입니다. 본고의 내용이 여러분의 학습이나 업무에 어느 정도 도움을 줄 수 있기를 바랍니다. 의문이 있으면 댓글로 의사소통을 할 수 있습니다.

좋은 웹페이지 즐겨찾기