테마
조민우는 지난달 버거 본사 DB에 「추정 오차 점검표」를 만들었다. 매일 08:00에 V$SQL_PLAN_STATISTICS_ALL에서 E-Rows와 A-Rows가 10배 넘게 차이 나는 계획 줄을 뽑아 메일로 보낸다(실행 통계는 STATISTICS_LEVEL = ALL로 띄운 점검용 세션에서 모은다). 점검표에는 날마다 서른 줄 가까이 올라온다.
선배는 어제 점검표에서 SQL 세 개를 골라 계획을 펼쳐 보고 이렇게 말했다. "여섯 줄 가운데 진짜는 세 줄이고, 그 셋도 원인은 한 줄이야. 점검표부터 고치자."
V$SQL_PLAN_STATISTICS_ALL: 계획 줄마다 한 행 · CARDINALITY = E-Rows(한 번 시작할 때의 추정) · LAST_STARTS = Starts · LAST_OUTPUT_ROWS = A-Rows(모든 Starts를 더한 값) · ID, PARENT_ID, DEPTH(계획의 줄과 부모 줄) · OPERATION, OPTIONSORD: 1,000,000행 · 22,422블록 · ORD_PK (ORD_NO) · ORD_X01 (ORD_DT, ORD_HMS): 날짜순 적재 · 하루 약 1,368건ORD_ITEM: 2,472,717행 · 55,265블록 · ORD_ITEM_PK (ORD_NO, ITEM_ID) · 주문당 평균 2.47줄ORD_DT LIKE '%0214')-- 「추정 오차 점검표」 매일 08:00 (조민우)
SELECT p.SQL_ID, p.CHILD_NUMBER, p.ID,
p.OPERATION || ' ' || p.OPTIONS AS op, p.OBJECT_NAME,
p.CARDINALITY AS e_rows,
p.LAST_OUTPUT_ROWS AS a_rows
FROM V$SQL_PLAN_STATISTICS_ALL p
WHERE p.LAST_OUTPUT_ROWS > p.CARDINALITY * 10
OR p.LAST_OUTPUT_ROWS * 10 < p.CARDINALITY
ORDER BY p.SQL_ID, p.CHILD_NUMBER, p.ID
[점검표 · 10월 14일 08:00 (서른한 줄 가운데 SQL 세 개의 여섯 줄)]
SQL ID OP OBJECT_NAME E_ROWS A_ROWS
① 6 INDEX RANGE SCAN ORD_ITEM_PK 2 3,378
① 7 TABLE ACCESS BY INDEX ROWID ORD_ITEM 1 3,378
② 3 TABLE ACCESS BY INDEX ROWID ORD 1000K 20
③ 1 HASH GROUP BY 67,252 184
③ 2 HASH JOIN 124K 6,758
③ 3 TABLE ACCESS FULL ORD 50,000 2,736
[① 「일 마감 메뉴 집계」 · ORD_DT = '20131230']
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 92 |00:00:00.02 | 4137 |
| 1 | HASH GROUP BY | | 1 | 92 | 92 |00:00:00.02 | 4137 |
| 2 | NESTED LOOPS | | 1 | 3383 | 3378 |00:00:00.02 | 4137 |
| 3 | NESTED LOOPS | | 1 | 3383 | 3378 |00:00:00.01 | 2771 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| ORD | 1 | 1368 | 1366 |00:00:00.01 | 39 |
|* 5 | INDEX RANGE SCAN | ORD_X01 | 1 | 1368 | 1366 |00:00:00.01 | 8 |
|* 6 | INDEX RANGE SCAN | ORD_ITEM_PK | 1366 | 2 | 3378 |00:00:00.01 | 2732 |
| 7 | TABLE ACCESS BY INDEX ROWID | ORD_ITEM | 3378 | 1 | 3378 |00:00:00.01 | 1366 |
-------------------------------------------------------------------------------------------------------
5 - access("O"."ORD_DT"='20131230')
6 - access("OI"."ORD_NO"="O"."ORD_NO")
[② 「최근 주문 20건」]
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 20 |00:00:00.01 | 5 |
|* 1 | COUNT STOPKEY | | 1 | | 20 |00:00:00.01 | 5 |
| 2 | VIEW | | 1 | 20 | 20 |00:00:00.01 | 5 |
| 3 | TABLE ACCESS BY INDEX ROWID| ORD | 1 | 1000K| 20 |00:00:00.01 | 5 |
| 4 | INDEX FULL SCAN DESCENDING| ORD_X01 | 1 | 20 | 20 |00:00:00.01 | 3 |
--------------------------------------------------------------------------------------------
1 - filter(ROWNUM<=20)
[③ 「밸런타인데이 메뉴 비교」]
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Used-Mem |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 184 |00:00:00.48 | 77687 | |
| 1 | HASH GROUP BY | | 1 | 67252 | 184 |00:00:00.48 | 77687 | 1250K (0)|
|* 2 | HASH JOIN | | 1 | 124K| 6758 |00:00:00.47 | 77687 | 3712K (0)|
|* 3 | TABLE ACCESS FULL| ORD | 1 | 50000 | 2736 |00:00:00.14 | 22422 | |
| 4 | TABLE ACCESS FULL| ORD_ITEM | 1 | 2472K| 2472K|00:00:00.21 | 55265 | |
-------------------------------------------------------------------------------------------------------
2 - access("OI"."ORD_NO"="O"."ORD_NO")
3 - filter("O"."ORD_DT" LIKE '%0214')
Starts와 STOPKEY로 설명하시오.