Skip to content

03화 · 날짜가 문자로 박혀 있다 ​

🗓️ 3월 18일 (월) · 02화에서 일주일 뒤 · 무대가 바뀐다: ora-dist (본사 레거시 유통) 오늘 배울 것: 묵시적 형변환은 둘 중 한쪽에서 일어난다. 지는 쪽이 컬럼이면 인덱스가 봉쇄된다. 그리고 오늘은 처음으로 빠른데 틀린 코드를 만난다.


파일 세 개 ​

파일무엇을 하나시간
읽는다1_사건.md지울 수 없는 가공을 만나고, 구제책의 대가를 재고, 2억 9천만 원이 어디로 샜는지 찾는 하루35분
한다3_실습과_기록.md9단계 · 형변환 방향 · 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 SCAN966383,578 (틀림)
LPAD 교정INDEX FAST FULL SCAN1,264400,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-RR27,390건 (정상)
YYYY-MM-DD1,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 6

ora-dist를 아직 안 만들었다면 00_자료준비/고객사/L3_ora-dist/README.md를 먼저 따라 한다. 전제: CF10 500,000행 · CF11 1,096,708행 · 보조 인덱스 0개 · 날짜 컬럼 6개가 VARCHAR2(8)

스크립트컨테이너무엇
실습/00_문자날짜_실험.sqlora-dist[A]~[G2]. 오늘 실습의 본체. 4,510 → 62
실습/01_형변환_방향.sqlora-dist어느 쪽이 변환되는가 · ORA-01722 · 10053
실습/02_구제책과_대가.sqlora-distFBI · 가상 컬럼 · 대소문자 하나로 59 → 1,406 · 적재 redo
실습/03_시각_패딩_검증.sqlora-dist☠️ 정합성 사고 재현 · 포맷 혼입 · 회귀 검증
실습/04_한빛유통_rev03.sqlora-hanorderQ1 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,00004화: 둘을 나란히 놓는다
조인에 남은 CF11 FULL 8,07506화: 오늘은 CF10 쪽만 고쳤다
SLE_HMS 데이터 교정 승인 (33,241건)09화: 이 시스템에 다시 온다
인덱스 1개 때문에 늘어난 redo 약 5.7MB14화: 청구서에 한 줄 더
E-Rows 고정 추정값과 문자 범위 선택도가 부정확한 문제10화
2019년 표기 규약이 왜 그때는 옳았는가18화: 판단의 유효기간

← 02화 「인덱스는 있는데 안 탄다」 · 교재 전체 안내 · 다음 → 04화 「인덱스를 태웠더니 더 느려졌다」