테마
10화 · 어제는 0.1초 오늘은 30초
🗓️ 5월 6일 (월) · 09화에서 일주일 뒤 · 무대
ora-hanorder(+ 저녁에ora-dist) 오늘 여는 것: 옵티마이저가 보는 숫자. SQL은 한 글자도 안 바뀌었는데 Q1과 Q2가 함께 무너진다. 그리고 01화 첫날 "고칠 수 있어. 근데 오늘은 아니야"라며 미뤄 둔 16배를 오늘 갚는다.
파일 세 개
| 파일 | 무엇을 하나 | 시간 | |
|---|---|---|---|
| 읽는다 | 1_사건.md | 일요일 밤의 재적재를 복사본에서 그대로 되풀이하고, 구내식당 식수표를 보고, E-Rows를 손으로 계산하고, 히스토그램이 고치는 곳과 망치는 곳을 확인하는 하루 | 40분 |
| 한다 | 3_실습과_기록.md | 재적재 재현 · 적재 방식별 통계 · 손계산 · 히스토그램 4종 · 확장 통계 · 본사 문자 날짜 · 정책 적용 · 정책서 1·2호 · T1 8호 | 80~110분 |
| 찾는다 | 2_파고들기.md | 추적 SOP · 손계산 공식 · 히스토그램 4종 · 통계가 만들어지는 길 · out-of-range · 확장 통계 · 10053 · 피드백과 지시문 · 09화 정정 | 필요할 때 |
1을 읽고 3을 따라 한다. 2는 막힐 때 펼친다. 시간이 없는 주에는 1_사건을 중단 지점 1까지 읽고 3_실습은 STEP 0, 1, 2, 4, 5, 8, 9만 한다(약 60분). STEP 8(정책 적용)은 건너뛰면 안 된다. 11화가 그 상태에서 시작한다.
지난 화에서 들고 오는 것
Q1 = 91 · Q2 = 5,869 | 02화와 03화의 커버링 인덱스, 06화의 힌트 제거로 얻은 값. 오늘 둘 다 무너진다 |
T_ORD_BIG 인덱스 | 3개(IX_ORD_BIG_DT · IX_ORD_BIG_DT_AMT · IX_ORD_BIG_DT_SEQ). 09화에서 하나가 늘었다 |
T_ORD_BIG 통계 | 선호도 FOR ALL COLUMNS SIZE 1 · 히스토그램 없음(00_자료준비의 04_상태복구.sql이 묶어 둔 상태) |
| 01화가 미룬 것 | ORD_ST = 'CANCEL'에서 E-Rows 333K vs A-Rows 20,000, 16배 |
| 02 · 03 · 04 · 08 · 09화가 넘긴 것 | "E-Rows가 정확해도 틀리는 경우" · 문자 날짜의 선택도 · 조인 카디널리티 · 포스트잇 계획의 999K · 935페이지의 Cost |
그리고 박팀장에게서 메시지가 하나 와 있다.
"등급별 매출 화면이 오늘 아침부터 30초 걸린답니다. 어제 당직이 볼 때는 0.1초였다고 합니다. 기간별 주문 조회도 느리답니다. 주말 사이 배포는 없었습니다."
오늘 여는 것 — 코드가 아니라 배치 스크립트의 한 줄
-- 2019-09-02 hd.kang
-- 적재 직후 통계 수집. 빼지 말 것.
-- 자동 통계 수집 창이 TRUNCATE 직후의 빈 테이블을 수집한 적 있음 (월요일 오전 등급별 매출 지연)【그때의 근거】 강도현은 2019년에 이미 이 일을 겪고 배치 맨 끝에 통계 수집 단계를 넣었다. 지난주 오과장은 그 단계를 뺐다. "인덱스가 세 개가 되고 적재가 길어져 04시를 넘긴다. 자동 통계 수집이 밤마다 돈다."라는 이유였다. 둘 다 맞는 말이었다. 자동 수집은 정말 밤마다 돈다. 빠진 것은 강도현이 적어 둔 "언제"였다.
오늘의 전개 — 네 번 틀리고, 정책을 쓴다
| 내가 믿은 것 | 측정 | |
|---|---|---|
| 1차 | "운영 서버가 바쁜가 보다" | 코드 같음 · 연습 DB는 91 · 5,869 그대로. 운영 T_ORD_BIG 통계는 0행 |
| — | 복사본에서 일요일 밤을 재현한다 | TRUNCATE → STALE → 빈 테이블 수집 → 100만 행 적재 → Q1 91 → 27,274 · Q2 5,863 → 1,026,852(E-Rows 1, 06화의 그 NL) |
| 2차 | "0이면 이상하니까 세어 보면 되잖아" | 통계를 지우면 동적 샘플링 → 해시 5,869. 하지만 0이라고 적혀 있으면 그대로 믿는다 |
| — | 응급 처치 | RESTORE_TABLE_STATS → Q2 5,868 · 재수집은 1초 남짓 |
| 3차 | "히스토그램을 전부 켜면 된다" | FREQUENCY로 CANCEL이 333,333 → 20,000. 그러나 SIZE 2 HYBRID에서는 CANCEL이 1, TOP-FREQUENCY에서는 최솟값과 최댓값이 1 |
| 4차 | "히스토그램을 넣으면 더 정확해진다" | CUS_ID와 함께 쓰면 167 → 10(실제 500)으로 더 틀린다. 컬럼 그룹은 그룹에도 히스토그램이 있어야 쓰인다 |
| 정책 | 「통계 수집 정책서」 1 · 2호 | 원본에 ORD_ST FREQUENCY · 6조각 계획 해시 변화 없음 · 배치에 수집 단계 복원 |
왜 이렇게 짰을까? 06화에서는 힌트를 지워 "판단을 옵티마이저에게 돌려줬다". 오늘은 그 판단의 재료(통계)가 0이 된다. 01화부터 끌고 온 16배를 고치는 도구(히스토그램)가 어디서 독이 되는지까지 본다. 그래서 오늘의 산출물은 처방이 아니라 정책이다.
오늘 배우는 한 문장
옵티마이저는 데이터가 아니라 통계를 보고 계획을 세운다. 그리고 통계는 열마다 따로 있다.
E-Rows는 산수다. 손으로 계산한 값과 맞는다면, 틀린 것은 옵티마이저가 아니라 통계나 가정이다. 판단을 옵티마이저에게 돌려줬다면, 옵티마이저가 보는 숫자를 누가, 언제, 어떻게 만드는지도 문서로 남겨야 한다.
E-Rows | 손계산 | 어디서 봤나 |
|---|---|---|
| 913 | 1,000,000 / 1,095 | 하루 |
| 28,336 | 1,000,000 × (30/1,094 + 1/1,095) | 02화 rev01 |
| 2,500 · 10,000 | 1,000,000 × 0.25% · 1% (가공한 열의 고정값) | 02화 rev00 · 03화 |
| 333,333 | 1,000,000 × 1/3 | 01화의 16배 |
| 10,000,000 | 200,000 × 5,000 / 100 | 08화의 50배 |
| 999,990 | ROWNUM을 떼고 본 포스트잇 조건 | 09화의 999K |
| 5,177 | 문자 날짜를 수직선 위의 숫자로 본 값 | 03 · 04화 |
🔑 오늘의 비유 — 구내식당 식수표
월요일 점심, 한식 줄은 반찬이 떨어졌는데 샐러드 바에는 샐러드가 산더미다. 벽의 화이트보드에는 "오늘의 식수 300명 · 메뉴 3종"이라고 적혀 있다. 영양사: "식수표대로 100인분씩 했어요. 메뉴별 신청은 안 받거든요." 선배: "옵티마이저도 영양사야. 사람을 보고 밥을 짓는 게 아니라 식수표를 보고 지어."
| 구내식당 | 오라클 |
|---|---|
| 식수표 (전날 집계 · 총인원과 메뉴 수) | 통계: NUM_ROWS · NUM_DISTINCT |
| 영양사의 조리 · 배식 계획 | 실행계획 |
| 실제로 온 사람 | 데이터: A-Rows |
| "메뉴 3종이니 100인분씩" | 히스토그램 없는 등치 조건: 1 / NDV(01화의 333K) |
| 메뉴별로 지난주에 나간 그릇 수 | FREQUENCY 히스토그램 |
| 일요일 밤 빈 사무실에서 센 월요일 식수표 | 적재 도중 빈 테이블에서 모은 통계(NUM_ROWS 0) |
| '3층 30명'과 '샐러드 2%'를 따로 곱해 나온 3층 샐러드 0.6그릇(3층은 다이어트 팀) | 상관 컬럼 AND: 독립 가정 |
| 한 명씩 주문받기 / 뷔페 라인 | NESTED LOOPS / HASH JOIN (← 06화 4층 카드함) |
(01화 커피 캐리어 = 블록 · 02화 출입대장 = 인덱스 · 06화 4층 카드함 = 조인 · 07화 탕비실 픽업 선반 = 버퍼 캐시 · 08화 회식 정산서 = 조인 곱셈 · 09화 출입대장 포스트잇 = 키 기반 페이징)
☠️ 오늘의 함정 — 히스토그램을 넣으면 더 정확해진다
01화의 16배는 FREQUENCY 히스토그램 한 줄로 사라진다. 333,333 → 20,000. 그래서 "다 켜자"는 말이 나오기 쉽다.
ORD_ST에 히스토그램을 넣으면 | 히스토그램 없음 | 있음 | 실제 |
|---|---|---|---|
ORD_ST = 'CANCEL' | 333,333 | 20,000 | 20,000 |
| 6월 AND CANCEL | 9,445 | 567 | 549 |
CUS_ID = 'CUS_000001' AND CANCEL | 167 | 10 ← 더 틀린다 | 500 |
| 같은 열에 버킷 2개(HYBRID)로 만들면 CANCEL은 | 333,333 | 1 | 20,000 |
이 데이터는 고객마다 상태가 하나뿐이다. 히스토그램에는 한 열의 분포만 담길 뿐, 열과 열 사이의 관계는 담기지 않는다. 그래서 옵티마이저는 두 선택도를 곱하기만 한다.
처방에는 리스크 칸이 있다. 정책서 1호의 4번 칸에 이 10을 적는다. "이 히스토그램은
'CANCEL'이 글자로 박혀 있을 때의 이야기"라는 점도 함께 적는다. 바인드 변수는 11화에서 다룬다.
실습 준비
bash
docker start ora-hanorder
docker start ora-dist # STEP 7 만09화를 마친 상태(T_ORD_BIG 인덱스 3개 · 선호도 SIZE 1 · 히스토그램 없음)에서 시작한다. ora-dist는 03화를 마친 상태다.
| 스크립트 | 컨테이너 | 무엇 | 걸리는 시간 |
|---|---|---|---|
실습/00_어제와_오늘.sql | ora-hanorder | 재적재 사고 재현 · 기각할 대안(힌트) · 정합성 · 통계 이력 · RESTORE · 재수집 | 30초~1분 |
실습/01_적재방식과_통계.sql | ora-hanorder | 23ai 온라인 통계 · 동적 샘플링 · 1만 행 · NL로 넘어가는 문턱 · 최댓값 밖 | 약 1분 |
실습/02_손계산.sql | ora-hanorder | 등치 · 범위 · 고정값 · 조인 · out-of-range · Cost · 09화 999K · 935페이지 · 추적 SOP | 약 5초 |
실습/03_히스토그램_4종.sql | ora-hanorder | FREQUENCY · HYBRID · HEIGHT BALANCED · 상관 AND · Statistics Feedback · 컬럼 그룹 · 표현식 통계 | 약 10초 |
실습/04_본사_문자날짜.sql | ora-dist | 문자 날짜의 수직선 · HYBRID · 표현식 통계 · 실제 상관 둘 · TOP-FREQUENCY | 약 15초 |
실습/05_정책_적용.sql | ora-hanorder | ★ 정책서 1호를 원본에 건다 · 6조각 전후 대조 · 종료 상태 확인 | 약 10초 |
실습/90_적재_일반INSERT.sql | ora-hanorder | (선택) 운영과 똑같이 인덱스 3개 + 일반 INSERT 100만 행 | 약 9분 (겹치면 20분 넘게) |
실습/91_10053_비용검산.sql | ora-hanorder | (선택) 10053으로 손계산 · 정렬 비용 검산 | 약 2초 |
실습/92_SPD_SYSDBA.sql | ora-hanorder (SYS) | (선택) SQL Plan Directive: 쌓이기는 하지만 기본 설정에서는 쓰이지 않는다 | 약 1분 30초 |
모든 스크립트는 맨 앞에서 초기화하고, 실험은 복사본(*_C10)에서 한 뒤 끝에서 지운다. 그래서 몇 번을 돌려도 결과가 같다. 원본에 손대는 것은 05_정책_적용.sql 하나뿐이고, 그마저도 통계 선호도와 통계만 바꾼다(데이터와 인덱스는 그대로). ⚠️ 05를 마지막으로 돌린 상태로 끝낸다. 01~09화의 스크립트나 00_자료준비/.../04_상태복구.sql을 돌리면 SIZE 1로 다시 수집해 히스토그램이 사라진다. 그때는 05를 다시 돌린다.
ℹ️ 이 교재는 Docker로 띄운 Oracle Database Free 한 대에서 모든 실습을 마친다. 운영에서 '자동 통계 수집이 적재 도중에 도는' 상황은 시각을 맞춰 기다릴 수 없으므로, 자동 수집이 하는 일(선호도대로, 인덱스까지 수집)을 손으로 대신해 재현한다. 일요일 창의 시각(06:00부터 20시간)은 실제 설정을 조회해 확인한다. 운영의 재적재(일반 INSERT)는 같은 통계 상태를 만드는 힌트(
APPEND NO_GATHER_OPTIMIZER_STATISTICS)로 빠르게 재현하고, 운영과 똑같은 재현은90에 따로 두었다.
이 화의 산출물
| 무엇 | 어디 |
|---|---|
「통계 수집 정책서」 ★ 새 표준 문서. 1호 T_ORD_BIG(ORD_ST FREQUENCY) · 2호 재적재 배치 | 40_보고서와_표준/템플릿/통계수집_정책서.md |
| E-Rows vs A-Rows 추적 SOP: 계획이 이상할 때 처음 30분 동안 할 일 | 2_파고들기.md §1 · 정책서 양식 안 |
| T1 리포트 8호: 처음으로 리비전이 없는 리포트. 대상은 SQL이 아니라 통계와 정합성 체크리스트 A~H | 3_실습과_기록.md STEP 9 |
| 리비전 | 없음. 오늘은 SQL을 한 글자도 고치지 않았다. Q1 rev03 · Q2 rev01 · Q3 rev01 · Q4 rev02 그대로 |
| 09화 기록 정정 | 935페이지 정렬 쪽 Cost는 'CPU'가 아니라 대부분 IO(TEMP를 쓸 것이라는 계산, 7,740) → 2_파고들기.md §11 |
이 화가 다음으로 넘기는 것
| 무엇 | 어디로 |
|---|---|
T_ORD_BIG.ORD_ST의 FREQUENCY 히스토그램(정책서 1호, 원본에 걸려 있다): 그 열을 바인드 변수로 받으면 | 11화: 바인드 피킹과 히스토그램 |
| 인덱스 3개를 단 채 일반 INSERT 100만 행이 오래 걸리는 이유(오과장의 04시와 통계 수집 단계가 부딪친 뿌리) | 14화: 청구서 |
본사 CF10의 정책서 후보(문자 날짜, 매장/직원 상관). 유통지원팀 회신 대기 | 회신이 오면 다시 잰다 |
정책서 1 · 2호의 재검토 조건(ORD_ST 새 값 · 적재 방식 변경 · 바인드) | 조건이 걸리면 다시 잰다 · 18화 원장 재측정 |
← 09화 「100페이지째부터 화면이 멈춘다」 · 교재 전체 안내 · 다음 → 11화 「같은 SQL이 수만 개」