테마
03화 · 날짜가 문자로 박혀 있다
🗓️ 3월 18일 (월) · 02화에서 일주일 뒤 · 무대가 바뀐다:
ora-dist(본사 레거시 유통) 오늘 배울 것: 묵시적 형변환은 둘 중 한쪽에서 일어난다. 지는 쪽이 컬럼이면 인덱스가 봉쇄된다. 그리고 오늘은 처음으로 빠른데 틀린 코드를 만난다.
파일 세 개
| 파일 | 무엇을 하나 | 시간 | |
|---|---|---|---|
| 읽는다 | 1_사건.md | 지울 수 없는 가공을 만나고, 구제책의 대가를 재고, 2억 9천만 원이 어디로 샜는지 찾는 하루 | 35분 |
| 한다 | 3_실습과_기록.md | 9단계 · 형변환 방향 · FBI vs 가상 컬럼 · T1 리포트 2호 + 정합성 사고 보고 | 70~90분 |
| 찾는다 | 2_파고들기.md | 형변환 규칙 · 문자 날짜 3조건 · Predicate의 이동 · 10053 · 날짜 교정표 | 필요할 때 |
1을 읽고 3을 따라 한다. 2는 막힐 때 펼친다. 시간이 없는 주에는 3_실습의 STEP 0, 1, 2, 5, 6만 한다(약 50분). STEP 5는 건너뛰면 안 된다. 이 화의 핵심이 거기 있다.
지난 화에서 들고 오는 것
Q1 = 91블록 | 02화 커버링 인덱스(ORD_DT, ORD_AMT)로 5,827 → 91 |
| 지난주의 교훈 | "컬럼 가공을 없애면 인덱스가 살아난다" |
| 지난주의 규칙 | "날짜 범위에 BETWEEN을 쓰지 마" |
| 02화가 미뤄 둔 것 | CF 1,000,000과 손익분기점 → 04화 · 인덱스 적재 비용 → 14화 |
그리고 오늘은 그 교훈과 규칙이 둘 다 흔들린다.
오늘의 전개 — 지울 수 없을 때 무엇을 하나
| 한 일 | 결과 | |
|---|---|---|
| 벽 | TO_DATE(SLE_DT,'YYYYMMDD')를 지우려 했다 | 지울 수가 없다. 컬럼이 VARCHAR2(8)이다 |
| 1차 | 지우는 대신 반대쪽을 문자로 맞췄다 | 4,510 → 62. 72배 |
| 덤 | 02화에서 금지한 BETWEEN을 썼는데 | 여기서는 맞다. 시분초가 없으니까 |
| 2차 | 숫자 리터럴로 비교한 화면 코드를 찾았다 | 5 → 1,406. 따옴표 두 개 차이 |
| 3차 | 구제책(FBI·가상 컬럼)을 쟀다 | 59. 그런데 채택하지 않았다 |
| 클라이맥스 | 회계와 안 맞는다던 집계를 봤다 | 966(틀림) vs 1,264(맞음). 빠른 쪽이 틀렸다 |
| 저녁 | 배운 규칙을 Q1에 거꾸로 적용했다 | rev03. 91 → 91 유지 |
왜 1차에서 한 번에 맞았을까? 02화에서 두 번 틀리며 배운 것을 여기서 쓰기 때문이다. 오늘 틀리는 자리는 따로 있다. 구제책을 쓸 뻔한 것과 빠른 코드를 통과시킬 뻔한 것이다.
오늘 배우는 두 문장
① 묵시적 형변환은 둘 중 한쪽에서 일어난다. 지는 쪽이 컬럼이면 인덱스가 봉쇄된다. 숫자는 문자를 이기고, 날짜도 문자를 이긴다. 그래서
문자컬럼 = 숫자는 망하고날짜컬럼 >= 문자는 산다.
② 문자로 적힌 날짜를 범위로 비교할 수 있는 것은 우연이 아니라 세 가지 규칙 덕분이다.길이가 같고, 0으로 채웠고, 큰 단위가 왼쪽에 있어야 한다. 하나라도 깨지면 글자 순서는 더 이상 시간 순서가 아니다.
🔑 오늘의 비유 — 문서고의 전표 보관함 (라벨 상자)
선반에 종이 상자가 줄지어 꽂혀 있고, 라벨에는 매직으로 쓴 숫자 여덟 개(20230601)가 적혀 있다. 라벨을 글자 순서대로 꽂았을 뿐인데 날짜 순서가 됐다. 길이가 같고(여덟 칸), 빈칸을 0으로 채웠고(06), 큰 단위가 왼쪽에 있기 때문이다. 그런데 "라벨을 숫자로 읽어서 가져와라"라고 하면, 상자를 전부 꺼내 라벨을 읽고 머릿속으로 바꿔 봐야 한다.
| 전표 보관함 | 오라클 |
|---|---|
| 매직으로 쓴 숫자 여덟 개 | VARCHAR2(8)에 담긴 날짜('20230601') |
| 글자 순서로 꽂았는데 날짜 순서가 됐다 | 문자 인덱스로 날짜 범위를 비교할 수 있다. 단, 길이가 같고 · 0으로 채웠고 · 큰 단위가 왼쪽일 때만 |
| 라벨을 숫자로 읽어서 고르기 | 문자 컬럼을 숫자 리터럴과 비교하면 묵시적 형변환(TO_NUMBER(컬럼))이 일어나 인덱스를 못 쓴다 |
| 상자를 전부 꺼내야 한다 | 인덱스가 있어도 전부 읽고 filter로 거른다 |
02화의 비유도 다시 나온다. 방문객 출입대장이다. 지난주에는 날짜를 글자로 바꿔서, 오늘은 글자를 숫자로 바꿔서 생긴 일이다. 방향만 반대일 뿐 같은 일이다.
☠️ 오늘의 함정 — 빠른데 틀린 코드
02화의 함정은 "내가 빠르면서 틀린 코드를 쓰려 한 것"이었다. 03화의 함정은 이미 운영에서 돌고 있는 코드가 그렇다는 것이다.
sql
WHERE SLE_HMS BETWEEN '090000' AND '180000' -- 영업시간 매출컬럼을 가공하지도 않았고, INDEX RANGE SCAN에 access까지 붙는다. 966블록. 보기엔 완벽하다.
그런데 SLE_HMS에는 앞자리 0이 날아간 행이 33,241건 있다. '95959'가 '180000'보다 크다.
| 계획 | Buffers | 결과 | |
|---|---|---|---|
| 원본 | INDEX RANGE SCAN | 966 | 383,578 (틀림) |
LPAD 교정 | INDEX FAST FULL SCAN | 1,264 | 400,197 (맞음) |
조인 기준으로 36,290건, 297,291,000원이 1년 내내 빠졌다. 빠진 것은 매일 아침 9시대 한 시간 동안의 매출이다.
Buffers만 보고 966을 통과시키면 정산이 틀리고,LPAD교정을 "느려졌다"는 이유로 반려하면 사고 난 코드로 되돌리는 셈이다. 그리고 이 문제는 SQL 튜닝으로는 고칠 수 없다. 원인이 데이터에 있기 때문이다.
두 번째 함정 — 에러도 안 나는 날짜
ora-hanorder에서 세션 설정 하나만 바꾸면 이렇게 된다.
sql
SELECT COUNT(*) FROM T_ORD_BIG WHERE ORD_DT >= '01-JUN-24';NLS_DATE_FORMAT | 결과 |
|---|---|
DD-MON-RR | 27,390건 (정상) |
YYYY-MM-DD | 1,000,000건. 에러도 없다. TO_DATE('01-JUN-24')가 0001-06-24로 해석된다 |
날짜를 변환할 때는 항상 포맷을 명시한다. TO_DATE(x,'YYYYMMDD')처럼 쓴다.
실습 준비 — 오늘은 컨테이너가 둘
bash
docker start ora-dist # 본사 레거시 유통 (pup/pup) — STEP 2~5
docker start ora-hanorder # 한빛유통 (sqlp/sqlp) — STEP 6ora-dist를 아직 안 만들었다면 00_자료준비/고객사/L3_ora-dist/README.md를 먼저 따라 한다. 전제: CF10 500,000행 · CF11 1,096,708행 · 보조 인덱스 0개 · 날짜 컬럼 6개가 VARCHAR2(8)
| 스크립트 | 컨테이너 | 무엇 |
|---|---|---|
실습/00_문자날짜_실험.sql | ora-dist | [A]~[G2]. 오늘 실습의 본체. 4,510 → 62 |
실습/01_형변환_방향.sql | ora-dist | 어느 쪽이 변환되는가 · ORA-01722 · 10053 |
실습/02_구제책과_대가.sql | ora-dist | FBI · 가상 컬럼 · 대소문자 하나로 59 → 1,406 · 적재 redo |
실습/03_시각_패딩_검증.sql | ora-dist | ☠️ 정합성 사고 재현 · 포맷 혼입 · 회귀 검증 |
실습/04_한빛유통_rev03.sql | ora-hanorder | Q1 rev03 · NLS 함정 |
모두 맨 앞에서 초기화하고 시작하므로 몇 번을 돌려도 결과가 같다. CF10의 데이터는 바뀌지 않는다. UPDATE 비용은 복사 테이블에서 재고 나서 지운다.
리비전은 리비전/Q1_rev03.sql에 남는다.
이 화의 산출물
| 무엇 | 어디 |
|---|---|
| T1 단건 SQL 튜닝 리포트 2호 ★ (Q1 rev02 → rev03 · 「채택하지 않은 대안」 칸을 처음 쓴다) | 3_실습 STEP 7 · T1 양식 |
| 정합성 사고 보고 (T4의 전신): 성능 리포트와 따로 쓰는 장애 보고 | 3_실습 STEP 7 별도 |
Q1_rev03 | 리비전/Q1_rev03.sql |
이 화가 다음으로 넘기는 것
| 무엇 | 어디로 |
|---|---|
CF10의 CF 9,921(테이블 4,583블록) ↔ T_ORD_BIG의 CF 1,000,000 | 04화: 둘을 나란히 놓는다 |
조인에 남은 CF11 FULL 8,075 | 06화: 오늘은 CF10 쪽만 고쳤다 |
SLE_HMS 데이터 교정 승인 (33,241건) | 09화: 이 시스템에 다시 온다 |
| 인덱스 1개 때문에 늘어난 redo 약 5.7MB | 14화: 청구서에 한 줄 더 |
E-Rows 고정 추정값과 문자 범위 선택도가 부정확한 문제 | 10화 |
| 2019년 표기 규약이 왜 그때는 옳았는가 | 18화: 판단의 유효기간 |
← 02화 「인덱스는 있는데 안 탄다」 · 교재 전체 안내 · 다음 → 04화 「인덱스를 태웠더니 더 느려졌다」