테마
01화 · 파고들기
조회용 문서다. 처음 읽을 때는
1_사건.md만으로 충분하다. 여기는 1년 뒤 "그게 뭐였지?" 싶을 때 돌아오는 자리다. 이야기는 빼고 정의와 표, 좌표만 담았다.
| 찾는 것 | 바로 가기 |
|---|---|
| 무대 전체 구조 (ERD · 테이블 설명) | §1 무대 |
| 블록이 정확히 뭔가 | §2 블록 |
Buffers · E-Rows · A-Rows 읽는 법 | §3 실행계획 |
| 측정 도구 3종은 언제 뭘 쓰나 | §4 계기판 |
| 6조각 원문과 「그때의 근거」 | §5 6조각 |
| SQL 문법이 낯설 때 | §6 문법 |
| 공식 문서 · 원서 좌표 | §7 좌표 |
1. 무대 — HANORDER의 두 시스템
컨테이너 ora-hanorder · 계정 sqlp · 테이블 16개. 구 시스템과 신 시스템이 한 계정에 섞여 있다.
구 시스템 (2015~) — 접두어 없는 영어 이름
| 테이블 | 행수 | 블록 | 성격 |
|---|---|---|---|
DEPT / EMP / SALGRADE | 4 / 14 / 5 | 5 / 5 / 5 | 오라클 고전 SCOTT. 개념 확인용 |
PRODUCTS | 2,000 | 21 | 상품 |
CUSTOMERS | 20,000 | 138 | 고객. grade A 1% / B 9% / C 90% |
ORDERS | 200,000 | 1,232 | 주문. status COMPLETE 90% / PENDING 8% / CANCEL 2%인덱스 3개: PK_ORDERS · ORDERS_X_CUST · ORDERS_X_DATE |
신 시스템 (2019~, 강도현) — M_ / T_ 접두어
| 테이블 | 행수 | 블록 | 성격 |
|---|---|---|---|
M_RGN | 8 | 5 | 지역 코드 |
M_ITM | 100 | 5 | 상품 |
M_ITM_PRC_HIS | 100 | 5 | 가격 이력 (ITM_ID, TO_YMD) |
M_CUS | 2,000 | 28 | 고객. CUS_GD A 1% / B 9% / C 90%. CUS_ID가 문자열이다('CUS_000123') |
T_ORD | 100,000 | 849 | 주문 |
T_ORD_DET | 200,000 | 828 | 주문상세 (주문당 1~3건) |
T_ITM_EVL | 5,000 | 43 | 상품평가. M:1:M 조인 폭증(08화)의 씨앗 |
T_ORD_BIG | 1,000,000 | 5,923 | PK도 인덱스도 없다. ORD_ST 90/8/2 · ORD_DT 3년치 |
T_ORD_STLM | 100,000 | 622 | 정산 결과 (01화 실습에서 생성) |
T_STLM_NUM | 1 | 5 | 채번 (01화 실습에서 생성) |
⚠️
M_CUS.CUS_ID는VARCHAR2,CUSTOMERS.cust_id는NUMBER다. 두 시스템을 한 쿼리에서 조인하면 안 된다. 두 시스템이 같은 계정에 있는 건 "같은 회사의 구·신 시스템"이라는 설정 때문이다.
ERD — 사람이 추론해서 그린 것
이 그림에 그은 선은 데이터베이스에는 없다. FK가 0개라서 컬럼 이름을 보고 추론해 그었다. Data Modeler로 자동 생성하면 선이 하나도 그려지지 않는다.
mermaid
erDiagram
M_RGN ||--o{ M_CUS : "RGN_ID (FK 없음)"
M_CUS ||--o{ T_ORD : "CUS_ID (FK 없음)"
M_CUS ||--o{ T_ORD_BIG : "CUS_ID (FK 없음)"
M_CUS ||--o{ T_ITM_EVL : "CUS_ID (FK 없음)"
T_ORD ||--o{ T_ORD_DET : "ORD_SEQ (FK 없음)"
M_ITM ||--o{ T_ORD_DET : "ITM_ID (FK 없음)"
M_ITM ||--o{ T_ITM_EVL : "ITM_ID (FK 없음)"
M_ITM ||--o{ M_ITM_PRC_HIS : "ITM_ID (FK 없음)"
M_CUS {
VARCHAR2 CUS_ID PK "CUS_000123 형식"
VARCHAR2 CUS_NM "고객명"
VARCHAR2 RGN_ID "지역"
VARCHAR2 CUS_GD "등급 A/B/C"
}
T_ORD {
NUMBER ORD_SEQ PK "주문번호"
VARCHAR2 CUS_ID "고객"
DATE ORD_DT "주문일시"
VARCHAR2 ORD_ST "상태"
NUMBER ORD_AMT "금액"
}
T_ORD_DET {
NUMBER ORD_SEQ PK "주문번호"
NUMBER ORD_DET_NO PK "순번"
VARCHAR2 ITM_ID "상품"
NUMBER ORD_QTY "수량"
}
T_ORD_BIG {
NUMBER ORD_SEQ "PK 없음"
VARCHAR2 CUS_ID "고객"
DATE ORD_DT "주문일시"
VARCHAR2 ORD_ST "상태"
NUMBER ORD_AMT "금액"
}
T_ITM_EVL {
VARCHAR2 ITM_ID PK "상품"
NUMBER EVL_LST_NO PK "평가순번"
VARCHAR2 CUS_ID "작성자"
NUMBER EVL_PT "평점"
}오늘 문제가 되는 경로는 왼쪽 위 삼각형이다. T_ORD_BIG ↔ M_CUS ↔ M_RGN으로 이어지는 길인데, Q2가 정확히 이 길을 탄다.
FK가 0개라서 생기는 일 세 가지
| # | 결과 | 어느 화에서 터지나 |
|---|---|---|
| 1 | 고아 데이터를 못 막는다. 없는 고객의 주문이 들어가도 DB는 통과시킨다 | — |
| 2 | 옵티마이저가 관계를 모른다. 조인 결과 건수를 제대로 추정하지 못한다 | 10화 |
| 3 | ERD 도구가 선을 못 그린다 | 01화에서 직접 확인 |
| + | 반대로 FK가 있으면, 인덱스가 없는 FK가 자식 테이블 전체에 락을 건다 | 12화 (ora-newshop) |
2. 블록
오라클에는 행을 읽는 기능이 없다. 오직 블록 단위로 읽는다.
| 항목 | 값 |
|---|---|
| 블록 크기 | 8,192 bytes (db_block_size 기본값) |
T_ORD_BIG 평균 행 길이 | 37 bytes |
| 블록당 행 수 | 169 |
| 검산 | 37 × 169 = 6,253. 남는 1,939 bytes는 블록 헤더 · 행 디렉터리 · PCTFREE 몫 |
sql
SELECT blocks, num_rows, ROUND(num_rows/blocks) AS rows_per_block, avg_row_len
FROM user_tables WHERE table_name = 'T_ORD_BIG';
SELECT value AS block_size FROM v$parameter WHERE name = 'db_block_size';BLOCKS와 Buffers가 다른 이유
통계상 BLOCKS는 5,923인데 전체 스캔의 Buffers는 5,826이다.
BLOCKS는 세그먼트에 할당된 블록 수다.- 전체 스캔은 HWM(High Water Mark)까지만 읽는다. HWM은 데이터가 실제로 닿아 본 지점이다.
100블록 안팎의 차이는 정상이다. 하지만 둘이 크게 벌어진다면 이상 신호다. 대량으로 삭제한 뒤 빈 껍데기가 남았다는 뜻이고, 05화 「지웠는데 왜 안 빨라지죠」가 바로 그 이야기다.
블록 내부 구조(ITL, 행 디렉터리, 행 이전/연결)는 05화에서 뜯어 본다. 오늘은 "8KB 덩어리, 169건"까지만 알면 된다.
3. 실행계획 한 장 읽기
xplan
------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 5826 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 5826 |
|* 2 | TABLE ACCESS FULL | T_ORD_BIG | 1 | 333K| 20000 |00:00:00.01 | 5826 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
2 - filter("ORD_ST"='CANCEL')읽는 순서 — 맨 아래부터 위로
들여쓰기가 깊은 것부터 실행된다. 위 실행계획은 ② TABLE ACCESS FULL → ① SORT AGGREGATE → ⓪ SELECT STATEMENT 순서로 실행된다.
열 하나씩
| 열 | 뜻 | 볼 때 |
|---|---|---|
Operation | 무엇을 했나 | TABLE ACCESS FULL은 처음부터 끝까지 훑었다는 뜻 |
Starts | 그 단계가 몇 번 실행됐나 | 1이어야 할 값이 100만이라면, 그게 바로 06화의 사건이다 |
E-Rows | 옵티마이저가 예상한 행수 | 통계를 근거로 한 짐작 |
A-Rows | 실제로 나온 행수 | 사실 |
A-Time | 걸린 시간 | 참고만 한다. 판단 근거로 쓰지 않는다 |
Buffers | 논리적 읽기 = 블록을 몇 번 만졌나 | 이 교재의 모든 판단 기준 |
Reads | 물리적 읽기 (디스크에서 가져온 블록) | 캐시가 비어 있으면 올라간다. 그래도 Buffers는 거의 변하지 않는다. 다만 디스크에서 곧장 읽은 블록은 세는 방식이 조금 달라서 몇 블록(0.1% 안팎) 적게 찍힐 수 있다 |
filter인가 access인가 — 오늘의 핵심
Predicate Information에 찍히는 표기는 두 가지다.
| 표기 | 뜻 | 비용 |
|---|---|---|
access | 그 조건으로 찾아갔다(인덱스로 위치를 좁힘) | 읽기 전에 거르므로 싸다 |
filter | 일단 다 읽고 나서 버렸다 | 100만 건을 읽고 98만 건을 버린다. 그래서 비싸다 |
위 실행계획은 filter("ORD_ST"='CANCEL')이다. 2만 건을 얻으려고 100만 건을 읽고 98만 건을 버렸다.
E-Rows vs A-Rows — 16배 오추정
ORD_ST는 값이 3종류지만 90 / 8 / 2로 치우쳐 있다. 옵티마이저는 종류 수만 알고 분포는 모른다.
예상: 1,000,000 ÷ 3 = 333,333 ← "세 종류니까 3분의 1이겠지"
실제: 20,000 ← CANCEL 은 2%
────────
16배 오차sql
SELECT ORD_ST, COUNT(*),
ROUND(RATIO_TO_REPORT(COUNT(*)) OVER ()*100, 2) AS pct
FROM T_ORD_BIG GROUP BY ORD_ST ORDER BY 2 DESC;이 오차를 고치는 도구가 히스토그램이고, 10화에서 다룬다. ⚠️ 자동 통계 수집이 밤사이 히스토그램을 만들어 버리면 이 오추정이 사라진다. 그럴 때는
00_자료준비/고객사/L1_ora-hanorder/04_상태복구.sql을 돌린다.
4. 계기판 — 도구 4종
| # | 도구 | 무엇을 보나 | 언제 |
|---|---|---|---|
| ① | EXPLAIN PLAN + DBMS_XPLAN.DISPLAY | 예상 계획 (실행 안 함) | 돌리기 겁나는 SQL |
| ② | /*+ GATHER_PLAN_STATISTICS */ + DISPLAY_CURSOR | 실제 계획 + 단계별 Buffers·A-Rows | 주력. SQL 하나를 자세히 볼 때 |
| ③ | SET AUTOTRACE ON | 계획 + consistent gets | 가장 간단히 훑어볼 때 |
| ④ | V$SQL | 여러 SQL을 한 표로 | 6조각을 나란히 비교할 때 |
sql
-- ① 예상
EXPLAIN PLAN FOR SELECT ...;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());
-- ② 실제 (주력)
SET SERVEROUTPUT OFF
SELECT /*+ GATHER_PLAN_STATISTICS */ ...;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(FORMAT => 'ALLSTATS LAST'));
-- ③ 한 번에
SET AUTOTRACE ON
SELECT ...;
SET AUTOTRACE OFF
-- ④ 여러 개 비교
SELECT sql_id, executions, buffer_gets,
ROUND(buffer_gets / GREATEST(executions,1)) AS buffers_per_exec
FROM v$sql WHERE sql_text LIKE 'SELECT /* BASE_%';⚠️ V$SQL의 숫자는 누적이다
같은 SQL을 두 번 돌리면 buffer_gets가 두 배로 보인다. SQL이 느려진 게 아니라 그 커서가 살아 있는 동안 쌓인 총합이라서 그렇다. 그러니 반드시 executions로 나눠서 본다.
📌 성능 뷰를 읽을 때는 이 숫자가 순간값인지, 누적값인지, 구간 델타인지 늘 따져 봐야 한다.
V$SQL,V$SYSSTAT,V$MYSTAT은 누적이고, AWR 리포트는 두 스냅샷의 델타다. 16화에서 이 구분이 그대로 다시 나온다.
함정 두 개
| 함정 | 증상 | 조치 |
|---|---|---|
SET SERVEROUTPUT ON인 채로 측정 | DISPLAY_CURSOR가 엉뚱하게 DBMS_OUTPUT.GET_LINES를 보여 준다 | 측정 전에 항상 SET SERVEROUTPUT OFF |
GATHER_PLAN_STATISTICS 누락 | A-Rows와 Buffers 칸이 비어 있다 | 힌트를 넣거나 ALTER SESSION SET STATISTICS_LEVEL=ALL |
권한이 없으면 안 보인다
ORA-01031이 나면 측정 권한이 없는 것이다. 00_측정권한_부여.sql을 실행한 뒤 재접속한다.
오늘 안 켜는 것
V$SESS_TIME_MODEL(시간이 어디로 갔나), V$SYSTEM_EVENT(대기 이벤트), AWR과 ASH는 16~17화에서 켠다. 오늘 켜 봐야 읽는 법을 모르니 숫자만 늘어난다.
5. 6조각 — 강도현이 남긴 것
야간 정산 패키지 PKG_ORD_STLM에 든 여섯 조각이다. 19화 내내 이 여섯 개를 고친다.
| ID | 무엇 | 처음 상태 | 몇 번 고치나 |
|---|---|---|---|
| Q1 | 기간별 주문 조회 | 날짜를 TO_CHAR로 문자 변환해 비교 | 3번 (02·03·04화) |
| Q2 | 고객등급별 매출 집계 | USE_NL 힌트로 못박음 + 스칼라 서브쿼리 | 06·08화 (07화는 재기만 한다) |
| Q3 | 상품평가 조인 | M:1:M 직접 조인으로 매출 중복 합산 | 1번 (08화) |
| Q4 | 정산 목록 페이징 | 전체 정렬 후 잘라내기 + 총건수 재조회 | 09·11화 |
| P1 | 정산 적재 배치 | 한 건씩 INSERT + 1,000건마다 커밋 | 3번 (13·14·15화) |
| N1 | 채번 | MAX(번호)+1 | 1번 (12화) |
Q1 — 기간별 주문 조회
sql
SELECT COUNT(*), SUM(ORD_AMT)
FROM T_ORD_BIG
WHERE TO_CHAR(ORD_DT, 'YYYYMMDD') BETWEEN '20240601' AND '20240630';그때의 근거:
2019-03-11 hd.kang커밋 로그 "날짜 타입 비교에서 시분초 때문에 하루가 누락되는 버그 수정. 문자로 통일."실제 버그를 고친 것이다.
ORD_DT에 시분초가 있으면BETWEEN ... AND DATE '2024-06-30'은 30일 00:00 이후를 놓친다. 방법이 틀렸을 뿐 동기는 옳았다. → 02·03화
Q2 — 고객등급별 매출 집계 (오늘의 범인)
sql
SELECT /*+ LEADING(o c) USE_NL(c) */
c.CUS_GD,
COUNT(*) AS ord_cnt,
SUM(o.ORD_AMT) AS amt,
MAX((SELECT r.RGN_NM FROM M_RGN r WHERE r.RGN_ID = c.RGN_ID)) AS rgn_sample
FROM T_ORD_BIG o, M_CUS c
WHERE c.CUS_ID = o.CUS_ID
GROUP BY c.CUS_GD;문제 두 가지가 겹쳐 있다. ① USE_NL 힌트: 100만 행을 한 건씩 돌며 M_CUS를 찾는다. ② 스칼라 서브쿼리: 지역명을 가져오려고 M_RGN을 다시 조회한다. 서브쿼리를 MAX()로 감싼 것은 GROUP BY 때문이다. 집계하지 않는 컬럼은 그대로 쓸 수 없어서 감쌌을 뿐이고, 이 MAX 자체는 성능과 상관이 없다.
그때의 근거:
2018-11-06 성능개선 회의록"등급별 집계 3초 → USE_NL 적용 후 0.4초. 적용 승인."그때 데이터는 2만 건이었다. 지금은 100만 건이다. 회의록의 숫자는 사실이었다. 그런데 그때도 블록으로 쟀다면 결론이 같았을까? → 06화
Q3 — 상품평가 조인 (가장 작은데 가장 위험)
sql
SELECT d.ITM_ID, SUM(d.ORD_QTY * d.UNT_PRC) AS amt, COUNT(*) AS cnt
FROM T_ORD_DET d, T_ITM_EVL e
WHERE e.ITM_ID = d.ITM_ID
GROUP BY d.ITM_ID ORDER BY amt DESC FETCH FIRST 5 ROWS ONLY;상품 하나에 평가가 N개면 그 상품 매출이 N번 더해진다. 841블록으로 빠르지만 답이 틀린다. → 08화
Q4 / P1 / N1
sql
-- Q4 : 전체를 정렬해 1,000건까지 자른 뒤, 그중 991~1000만 쓴다
SELECT * FROM (SELECT ROWNUM rn, a.*
FROM (SELECT ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT
FROM T_ORD_BIG ORDER BY ORD_DT DESC, ORD_SEQ DESC) a
WHERE ROWNUM <= 1000)
WHERE rn > 990;
-- Q4c : 화면 하단의 "총 N건"을 위해 테이블을 한 번 더 읽는다
SELECT COUNT(*) FROM T_ORD_BIG;Q4 5,826 + Q4c 5,826 = 11,652블록. 테이블 전체가 5,826이니, 10건을 보여 주려고 한 페이지마다 테이블 전체를 두 번 읽는 셈이다. 사용자가 "다음 페이지"를 누를 때마다 이 일이 되풀이된다. → 09화
sql
-- P1 : 커서 FOR 루프 + 1,000건마다 COMMIT
-- 근거: "트랜잭션은 짧게 (2020.07 강도현)"
-- ← 12화의 재발방지책이 13화의 장애(ORA-01555)가 된다
-- N1 : SELECT NVL(MAX(STLM_SEQ),0)+1 FROM T_ORD_STLM
-- 근거: "시퀀스는 구멍이 생겨서" ← 감사 지적을 받은 적이 있었다
-- 채번 테이블 T_STLM_NUM 이 있는데도 쓰지 않는다. LAST_NO 는 0인 채로 남아 있다원문 전체는 실습/00_관통코드_생성.sql에 있다.
6. 오늘 쓴 SQL 문법
처음 보는 게 있으면 여기서 확인한다. 아래는 모두 조회용 딕셔너리 뷰다. 일반 테이블이 아니라, 오라클이 자기 자신에 관한 정보를 보여 주는 창구다.
| 쓴 것 | 무엇 | 비고 |
|---|---|---|
user_tables | 내 계정의 테이블 목록 + 통계 | num_rows·blocks는 통계를 수집한 시점의 값이다. 실시간 값이 아니다 |
user_constraints | PK · FK · UK · CHECK | constraint_type: P=PK, R=FK(Reference), U=UK |
user_cons_columns | 그 제약이 어느 컬럼에 걸렸나 | 제약명으로 조인한다 |
user_indexes | 인덱스 목록 | uniqueness는 UNIQUE/NONUNIQUE |
user_tab_col_statistics | 컬럼별 통계 · 히스토그램 유무 | 10화에서 주로 쓴다 |
v$parameter | 초기화 파라미터 | db_block_size 등 |
v$sql | 공유 풀에 살아 있는 SQL과 그 누적 통계 | 누적이다(§4 참조) |
sql
-- DECODE : 값에 따라 다른 것을 반환 (CASE 의 축약형)
DECODE(constraint_type, 'P','PK', 'R','FK', 'U','UK')
-- = CASE constraint_type WHEN 'P' THEN 'PK' WHEN 'R' THEN 'FK' ... END
-- RATIO_TO_REPORT : 전체 합계 대비 비율을 구하는 분석함수
ROUND(RATIO_TO_REPORT(COUNT(*)) OVER () * 100, 2) AS pct
-- OVER () 가 "전체를 한 덩어리로 보라"는 뜻. 비율 계산에 서브쿼리가 필요 없다
-- LISTAGG : 여러 행을 한 줄로 이어 붙인다 (컬럼 목록 만들 때)
LISTAGG(column_name, ',') WITHIN GROUP (ORDER BY position)
-- NULLS LAST : 정렬할 때 NULL 을 맨 뒤로 (통계 없는 테이블이 위로 오는 걸 막는다)
ORDER BY num_rows DESC NULLS LAST
-- FETCH FIRST n ROWS ONLY : 상위 n건 (12c 이상. 옛날 ROWNUM 방식을 대체)📌
USER_*는 내 것,ALL_*은 내가 볼 수 있는 것,DBA_*는 전부를 보여 준다. 같은 뷰를 범위만 셋으로 나눈 것이다.
7. 원천 좌표
좌표 없이 "다시 읽었다"고만 쓰면 인정하지 않는다. 전체 지도는
00_자료준비/06_원서와_공식문서_지도.md에 있다.
성능고도화 (주 원천)
PDF 페이지 = 책 페이지 − 2(I·II 양권 공통). 절별 페이지는
성능고도화I_목차색인.md*(공개 사이트에는 없음)*에 있다.
| 주제 | 권-장-절 | 책 p. |
|---|---|---|
| 오라클 성능관리 전반 | I권 03장 | 153 |
| Explain plan | I권 03장 01절 | 155 |
| AutoTrace | I권 03장 02절 | — |
| SQL 트레이스 | I권 03장 03절 | — |
DBMS_XPLAN 패키지 | I권 03장 04절 | — |
V$SYSSTAT | I권 03장 05절 | — |
| Response Time Analysis (OWI) | I권 03장 07절 | — |
오라클 공식문서
| 주제 | 매뉴얼 | 장 제목 | 관련 뷰 |
|---|---|---|---|
| 성능 개선 방법론 | tgdba 3장 | Performance Improvement Methods | — |
| Time Model · 대기이벤트 | tgdba 5장 | Measuring Database Performance | V$SESS_TIME_MODEL |
| SQL 튜닝 접근법 | tgsql | SQL Performance Methodology | — |
| 실행계획 표시 | arpls | DBMS_XPLAN | DISPLAY_CURSOR |
| 논리적 읽기의 정의 | cncpt | Memory Architecture > Database Buffer Cache | V$BH |
링크 규칙: https://docs.oracle.com/en/database/oracle/oracle-database/{19|26}/{약칭}/{페이지}.html오늘 읽으면 가장 남는 한 장
tgdba 3장 Performance Improvement Methods. 오늘 선배가 한 말("증언이 아니라 측정", "무엇을, 어떤 조건에서, 어떤 단위로 쟀는지")이 모두 이 장에 공식 용어로 적혀 있다. 이 교재 커리큘럼의 뼈대이기도 하다(3장 → 5장 → 6장 → 9장 → 7장 → 10장).
다음 화가 여기서 가져가는 것
| 화 | 기준선 | 무엇을 하나 |
|---|---|---|
| 02 | Q1 = 5,827 | 인덱스를 만들고 안 타는 걸 확인한 뒤 SQL을 고친다 |
| 04 | 02화에서 만든 인덱스 | 커피 캐리어가 다시 나온다. 같은 인덱스인데 왜 하나만 느린가 |
| 06 | Q2 = 1,026,849 | 힌트를 떼면 5,869, 175분의 1로 줄어든다 |
| 08 | Q3의 정합성 오류 | 중복 합산을 고친다 |
| 09 | Q4+Q4c ≈ 11,652 | 페이징 재설계 |
| 10 | E-Rows 333K vs A-Rows 20,000 | 히스토그램으로 오추정을 고친다 |
| 12 | FK 0개 · N1의 MAX+1 | FK가 있는 시스템과 비교한다 |
| 13 | P1의 커밋 100회 | redo size와 ORA-01555 |
| 18 | W01 원장 전체 | 6조각을 전부 재측정해 나란히 놓는다 |