핵심 인사이트 (3줄 요약)
- 본질: 스칼라 서브쿼리(Scalar Subquery)는 SQL의
SELECT절 위치에 괄호( )를 치고 들어가는 쿼리로, 메인 쿼리의 출력 결과물(Row) 한 줄이 생성될 때마다 매번 실행되어 반드시 딱 1개의 행(Row)과 1개의 열(Column) 값만을 리턴해야 하는 엄격한 제약을 갖는다.- 가치: 복잡한
JOIN과GROUP BY구문을 쓰지 않고도, 다른 테이블에 있는 특정 코드의 이름(예: 부서명, 직급명)이나 집계 값(예: 사원의 총 주문 건수)을 메인 결과 집합에 직관적이고 손쉽게 덧붙일 수 있어 쿼리 작성의 가독성과 편의성을 극대화한다.- 융합: 작성은 달콤하지만, 메인 쿼리가 10만 건이면 서브쿼리가 10만 번 반복 실행되는 치명적인 루프(Nested Loop) 성능 병목을 유발한다. 따라서 옵티마이저의 스칼라 서브쿼리 캐싱(Caching) 기능이 어떻게 작동하는지 이해하고, 캐시 효율이 떨어지는 대용량 데이터에서는 아키텍처를 과감히 **아우터 조인(Outer Join)**으로 뜯어고쳐야 한다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
-
개념: '스칼라(Scalar)'는 방향성이 없이 크기만 존재하는 단일한 '값(Value)'을 뜻하는 수학 용어다. DB 쿼리에서 스칼라 서브쿼리는 괄호 안의 SELECT 문이 여러 개의 데이터 덩어리가 아닌, 오직 1개의 단일 값(예: '영업부', '50000', '홍길동')만을 반환하도록 강제된 특수한 뷰(View) 덩어리다.
-
필요성: 메인 테이블인
사원테이블에는부서ID만 적혀있다. 결과 화면에는 숫자가 아니라 '영업부'라는 글자를 찍어주고 싶다. 정석대로라면부서테이블을 끄집어내어 조인(Join)을 걸어야 한다. 하지만 테이블이 5개, 10개로 늘어나고 단순히 이름(코드성 데이터) 하나만 쏙 빼오고 싶을 때마다 거대한 조인절을 엮는 것은 코드를 뚱뚱하게 만든다. "복잡하게 조인으로 합치지 말고, 메인 테이블 데이터를 쭉쭉 뽑아내는 와중에 옆 테이블에서 필요한 값 하나만 핀셋으로 쏙쏙 집어와서 붙여줄 수 없을까?"라는 편의성에 대한 갈증이 스칼라 서브쿼리를 낳았다. -
💡 비유: 일반 조인이 수박과 사과와 바나나를 거대한 도마 위에 다 쏟아붓고 한꺼번에 섞어서 과일 샐러드를 만드는 방식이라면, 스칼라 서브쿼리는 완성된 수박 샐러드 접시가 컨베이어 벨트를 타고 하나씩 지나갈 때마다, 요리사가 냉장고(다른 테이블) 문을 열고 체리(단일 값)를 딱 1알씩만 꺼내서 수박 샐러드 위에 핀셋으로 얹어주는 장식 작업과 같습니다.
-
등장 배경:
- 코드성 테이블(Lookup Table) 조인의 불편함: 성별 코드, 부서 코드, 직급 코드 등 단순한 1:1 매핑 테이블을 가져오기 위해 SQL의 덩치가 커지고 쿼리 짜기가 너무 귀찮았다.
- 절차적 사고의 이식: 프로그래머들은 "직원 1명을 조회하고 ➔ 그 직원의 부서명을 조회하는 함수를 부르자"는 절차적(Procedural) 사고에 익숙했다. SQL의
SELECT절에 함수를 부르듯 서브쿼리를 박아 넣는 문법은 이들의 사고방식과 완벽히 일치했다.
┌─────────────────────────────────────────────────────────────┐
│ 스칼라 서브쿼리의 작동 원리 (반복 루프의 마법과 함정) │
├─────────────────────────────────────────────────────────────┤
│ │
│ [ 쿼리 작성 ] │
│ SELECT 사원.사원번호, │
│ 사원.사원이름, │
│ ( SELECT 부서명 FROM 부서 │
│ WHERE 부서.부서ID = 사원.부서ID ) AS 소속부서명 │
│ FROM 사원; │
│ │
│ [ DB 엔진의 내부 작동 방식 (Nested Loop) ] │
│ │
│ 메인 테이블 (사원) 100만 명 스캔 시작! │
│ ──────────────────────────────────────────────── │
│ 행 1: [1001, 홍길동, 부서ID: A] │
│ ➔ (스칼라 발동!) "부서 테이블에서 A의 이름을 찾아와!" ➔ (영업부 반환) │
│ │
│ 행 2: [1002, 김철수, 부서ID: B] │
│ ➔ (스칼라 발동!) "부서 테이블에서 B의 이름을 찾아와!" ➔ (마케팅 반환) │
│ │
│ 행 ... (100만 번 반복 💥💥💥) │
│ │
│ 행 1,000,000: [..., 부서ID: C] │
│ ➔ (스칼라 발동!) "부서 테이블에서 C의 이름을 찾아와!" ➔ (개발부 반환) │
│ │
│ 🌟 결과: 코드는 1줄이지만, DB 내부에선 100만 번의 반복적인 서브쿼리 호출과│
│ 디스크 I/O가 터진다. 이것이 스칼라 서브쿼리의 소름 돋는 이중성이다! │
└─────────────────────────────────────────────────────────────┘
[다이어그램 해설] 스칼라 서브쿼리는 FROM 절에 붙는 인라인 뷰(딱 한 번만 실행됨)와 차원이 다른 짐승이다. 메인 쿼리의 행(Row) 수가 100만 건이면, SELECT 절에 있는 스칼라 서브쿼리는 100만 번 실행된다. (마치 Java 코드 안에서 100만 번 도는 for 문 안에 SQL을 넣고 돌리는 것과 같다). 만약 괄호 안의 서브쿼리가 인덱스(Index)를 제대로 타지 않아 테이블 풀스캔(Full Scan)을 한다면? 100만 번의 풀스캔이 발생하며 데이터베이스 CPU는 100%를 찍고 장렬히 전사한다. 작성은 한없이 달콤하지만 대용량 환경에서는 시스템을 죽이는 시한폭탄이다.
- 📢 섹션 요약 비유: 메인 쿼리가 10만 명의 시험지를 채점하는 선생님이라면, 스칼라 서브쿼리는 학생 1명의 시험지를 넘길 때마다 교무실(다른 테이블)에 있는 학생 기록부 폴더를 열어 부모님 이름을 확인하고 다시 제자리로 돌아오는 미친 듯한 10만 번의 왕복 달리기입니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
절대 규칙 1: 단일 행(Single-Row) 반환의 강제
스칼라 서브쿼리가 에러를 내뱉지 않고 정상 작동하려면 두 가지 제약을 목숨처럼 지켜야 한다.
- 단 1개의 행(Row)만 반환할 것: 만약 홍길동이 부서를 2개(영업부, 총무부 겸직) 가지고 있어서 서브쿼리 결과가 2줄이 나오면,
ORA-01427: single-row subquery returns more than one row(단일 행 서브쿼리가 둘 이상의 행을 리턴했습니다) 에러가 뻥 터지며 쿼리 전체가 죽어버린다.- 해결책: 데이터 모델 상 다대일(N:1) 관계로 1줄만 나옴이 보장되거나, 만약을 대비해 괄호 안에
MAX(부서명)이나LIMIT 1 (ROWNUM=1)을 걸어 무조건 1개만 나오도록 멱살을 잡아야 한다.
- 해결책: 데이터 모델 상 다대일(N:1) 관계로 1줄만 나옴이 보장되거나, 만약을 대비해 괄호 안에
- 단 1개의 열(Column)만 반환할 것:
(SELECT 부서명, 위치 FROM 부서...)처럼 두 개 이상의 컬럼을 리턴하면 "내가 이 두 값을 한 칸에 어떻게 우겨넣냐!"라며 구문 에러를 낸다. 오직 스칼라(1차원 단일 값)만 허용된다.
절대 규칙 2: 데이터가 없을 경우의 우아함 (Outer Join과 동치)
메인 쿼리의 사원 중 '신입사원'이라 아직 부서 배정을 못 받아 부서 테이블에 매칭되는 데이터가 0건일 때는 어떻게 될까?
- 스칼라 서브쿼리는 에러를 내지 않고 조용히
NULL값을 반환한다. 그리고 메인 쿼리의 신입사원 데이터 행(Row)은 사라지지 않고 그대로 화면에 출력된다. - 즉, 스칼라 서브쿼리의 논리적 아키텍처는 일반 조인(Inner Join)이 아니라
LEFT OUTER JOIN과 100% 완전히 똑같다.
Ⅲ. 융합 비교 및 다각도 분석
비교 1: 스칼라 서브쿼리 캐싱 (Scalar Subquery Caching) 메커니즘
100만 번의 왕복 달리기를 하다가 옵티마이저(Optimizer)가 바보가 아닌 이상 이걸 그대로 두진 않는다. DB 엔진(특히 오라클)은 스칼라 쿼리의 무식한 반복을 구원하기 위해 메모리 캐싱(Caching) 이라는 기적의 아키텍처를 도입했다.
- 작동 원리: "홍길동의 부서ID가 10번이네. 부서 테이블에 뛰어가서 '영업부'를 찾아왔어. 자, 이제 이 입력값
10과 결과값영업부쌍을 내 메모리(해시 맵 캐시)에 적어놔야지." ➔ 다음 행인 김철수도 부서ID가 10번이다. DB 엔진은 "어? 입력값 10번? 아까 메모리에 '영업부'라고 찾아둔 거 있잖아! 부서 테이블(디스크)에 갈 필요 없이 그냥 캐시에서 꺼내 줘!" - 효과: 사원 100만 명의 데이터를 뽑을 때, 실제 부서 테이블에는 부서 개수(예: 20번)만큼 딱 20번만 디스크 조회를 찌르고, 나머지 99만 9,980번은 메모리에서 0초 만에 꺼내오는 극한의 성능 튜닝이 완성된다.
┌───────────────────────────────────────────────────────────────┐
│ 스칼라 캐싱의 성공(Hit) vs 실패(Miss)에 따른 성능 딜레마 │
├───────────────────────────────────────────────────────────────┤
│ │
│ [ 🚀 캐싱 히트(Hit) 극대화 상황: 입력값의 종류가 적을 때 ] │
│ - 메인 데이터: 100만 건 / 부서 코드의 종류(Distinct): 20개 │
│ - 스칼라 캐시 맵 크기: 20칸 (초소형) │
│ ➔ 실제 디스크 조회 20번 + 캐시 적중 999,980번 = **조인보다 빠름!** │
│ │
│ [ 💥 캐싱 실패(Miss) 파멸 상황: 입력값의 종류가 무한대일 때 ] │
│ - 메인 쿼리: 100만 건 회원 조회 │
│ - 스칼라 쿼리: (해당 회원의 **'가장 최근 결제일시'** 추출) │
│ - 입력값의 종류(Distinct): 회원 100만 명의 고유 ID (100만 개) │
│ ➔ 스칼라 캐시 맵 크기 초과! (메모리 꽉 차서 계속 캐시 밀려나고 삭제됨) │
│ ➔ 실제 디스크 조회 100만 번 풀 가동! (Nested Loop 100만 번 💥) │
│ ➔ **조인 대비 수천 배 느려지고 DB 전체가 뻗어버림!** │
└───────────────────────────────────────────────────────────────┘
[다이어그램 해설] 스칼라 서브쿼리를 사용할지 말지 결정하는 유일무이한 아키텍트의 판단 기준이다. 입력값의 종류(Distinct Value)가 코드성 데이터(부서코드, 성별, 상태코드)처럼 몇 개 안 될 때는 캐시 효율이 99%에 달해 조인(Join)의 해시 빌드업 비용조차 씹어먹는 최고의 무기가 된다. 하지만 회원별 최근 결제액, 개별 계좌의 잔고 등 모든 행마다 서브쿼리의 입력값이 100% 다르게 들어가는 로직을 스칼라로 짜버리면, 캐시 맵에 저장하는 족족 쓸모가 없어져(캐시 미스) 무차별 랜덤 디스크 I/O가 터진다. 이럴 때는 무조건 괄호를 뜯어내고 LEFT OUTER JOIN 과 인라인 뷰(Inline View)로 집합 전체를 한 방에 묶어서 쿼리를 엎어버려야 한다.
- 📢 섹션 요약 비유: 스칼라 쿼리 캐싱은 동네 중국집 배달부입니다. 동네 아파트(부서 종류 20개) 배달은 머릿속에 지도가 다 있어서 눈 감고도 빛의 속도로 가지만, 전국 100만 명(고객 ID 100만 개)의 집을 배달해야 한다면 길을 찾다가 과로사로 쓰러집니다. 이때는 대형 택배 트럭(조인)으로 한꺼번에 쓸어 담아 배송해야 합니다.
Ⅳ. 실무 적용 및 기술사적 판단
실무 시나리오
-
시나리오 —
LEFT OUTER JOIN을 대체하는 코드값 추출 튜닝: 기존 레거시 쿼리에서 주문 내역을 뽑는데, '주문상태코드', '결제수단코드', '배송사코드' 이름을 가져오기 위해 공통코드 테이블을 아우터 조인으로 3번이나 줄줄이 걸어두었다. 테이블 4개가 조인되면서 쿼리가 뚱뚱해지고 옵티마이저가 조인 순서(Driving/Driven)를 헷갈려 이상한 풀스캔 실행 계획으로 빠졌다.- 판단: 공통코드 테이블은 종류가 몇십 개 안 되는 전형적인 캐시 히트(Cache Hit) 100% 타겟이다. 아키텍트는 3개의 아우터 조인을 과감히 잘라내고, SELECT 절에 3개의 스칼라 서브쿼리 괄호로 바꿨다. 쿼리의 가독성이 3배 좋아졌고, 메인 주문 테이블의 조인 경로가 단순해지며 옵티마이저가 최적의 실행 계획을 찾아내 쿼리가 0.1초 만에 떨어졌다.
-
시나리오 — 최신 옵티마이저의 마법, 스칼라 서브쿼리 언네스팅 (Unnesting): 개발자가 1,000만 건의 결제 테이블을 뽑으면서 SELECT 절에 스칼라 쿼리로 유저 테이블을 찌르는 (고유값 1,000만 개인 파멸적 스칼라) 안티패턴 쿼리를 짰다. DBA가 식겁해서 DB 모니터링을 봤는데, 쿼리가 안 뻗고 5초 만에 정상적으로 튀어나왔다.
- 판단: Oracle 12c 및 MySQL 8.0 등 최신 RDBMS 옵티마이저의 지능이 괴물같이 진화했기 때문이다. 옵티마이저(CBO)가 개발자의 멍청한 스칼라 쿼리를 딱 보고 "어휴, 이거 그대로 돌리면 Nested Loop 1,000만 번 돌아서 뻗겠네. 내가 몰래 괄호 찢어내고 아우터 조인(Outer Join) + 해시 조인(Hash Join) 구조로 뒤에서 몰래 변환(Rewrite)해서 실행해 줘야지"라고 스스로 마개조를 해버린 것이다. (Subquery Unnesting). 다만, 이 기능은 집계 함수(SUM, MAX)가 섞여 있으면 가끔 풀리지 않아 폭탄이 터지므로 옵티마이저만 믿고 난사하면 안 된다.
┌─────────────────────────────────────────────────────────────┐
│ 실무 아키텍처: 스칼라 서브쿼리의 성능 폭발과 조인 변환 회피 기법 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [❌ 주니어의 지옥 쿼리: N번 디스크 스캔 💥] │
│ SELECT 유저ID, │
│ (SELECT SUM(결제액) FROM 결제내역 WHERE 유저ID = M.유저ID) │
│ FROM 100만유저_테이블 M; │
│ ➔ (치명적 문제: 유저 100만 명마다 결제내역 썸을 100만 번 쿼리함!) │
│ │
│ [✅ 시니어의 정답 쿼리 1: 인라인 뷰 집합 조인 🚀] │
│ SELECT M.유저ID, V.총결제액 │
│ FROM 100만유저_테이블 M │
│ LEFT OUTER JOIN ( │
│ SELECT 유저ID, SUM(결제액) AS 총결제액 │
│ FROM 결제내역 GROUP BY 유저ID │
│ ) V ON M.유저ID = V.유저ID; │
│ ➔ (해법: 결제내역을 단 1번 풀스캔해서 메모리에 통째로 뭉쳐두고 해시조인 쾅!)│
│ │
│ [🔥 초고수의 극강 꼼수: 스칼라 내 인라인 뷰 캐시 강제화] │
│ SELECT 유저ID, │
│ (SELECT 총결제액 FROM ( │
│ SELECT 유저ID, SUM(결제액) AS 총결제액 FROM 결제내역 │
│ GROUP BY 유저ID │
│ ) V WHERE V.유저ID = M.유저ID) │
│ FROM 100만유저_테이블 M; │
│ ➔ (해법: 옵티마이저가 병합 못하게 인라인 뷰로 벽을 치고 메모리 뷰 생성 유도)│
└─────────────────────────────────────────────────────────────┘
[다이어그램 해설] 스칼라 쿼리 튜닝의 교과서다. 스칼라 쿼리 안에 SUM 같은 무거운 집계 연산이 껴있으면, 메인 쿼리 한 줄 뽑을 때마다 결제내역 테이블 수만 건을 뒤져서 더하기 연산을 하는 참사가 터진다. 시니어는 이 구조를 과감히 찢어서 FROM 절의 인라인 뷰 덩어리로 내린다. 즉, "미리 결제내역 전체를 한 번만 스캔해서 사람별 총액을 표로 만들어두고(인라인 뷰), 그걸 메인 유저 테이블이랑 조인시켜(해시 조인)!"라고 선언적 집합(Set) 사고로 전환하는 것이 스칼라 병목 튜닝의 90%를 차지하는 궁극기다.
도입 체크리스트
- 기술적: 스칼라 서브쿼리 안에 사용된 조인 조건(
WHERE 안쪽.ID = 바깥쪽.ID) 컬럼들에 **적절한 인덱스(Index)**가 완벽히 걸려 있는가? 스칼라 쿼리가 10만 번 돌더라도 인덱스 스캔(Index Unique Scan)을 타면 메모리 B-Tree 루트만 찍고 0.001초 만에 내려오지만, 풀스캔을 타면 디스크 전체를 10만 번 훑어 서버가 100% 폭파된다. - 운영·보안적: 1개의 스칼라 쿼리를 던졌는데
Subquery returns more than 1 row예외 에러가 간헐적으로 떨어지는 데이터를 방치했는가? 당장 로직을 수정하여 괄호 안쪽에ROWNUM=1또는LIMIT 1을 붙여서 논리적 오류가 시스템 마비로 번지지 않도록 방어벽을 세워야 한다.
안티패턴
-
스칼라 쿼리 안에서 또 다른 스칼라 쿼리를 중첩 (Nested Scalar):
SELECT (SELECT (SELECT...) FROM ...) FROM ...처럼 괄호 안에 괄호를 파고드는 러시아 인형 쿼리. 옵티마이저는 이 기괴한 구조를 캐싱하지도 병합하지도 못하고, 가장 멍청한 최악의 무한 중첩 루프(Nested Loop) 실행 계획으로 추락시켜 버린다. 1만 건만 조회해도 쿼리 결과가 1시간 뒤에 나오는 마법을 볼 수 있다. -
📢 섹션 요약 비유: 스칼라 서브쿼리는 아주 예리하고 정밀한 수술용 메스입니다. 종양(코드값 1개)을 톡 떼어낼 때는 기가 막히게 편하고 우아하지만, 이걸 들고 통나무 1,000개(100만 건 대용량 썸 집계)를 자르려고 들면 팔이 부러지고 칼이 부러집니다. 통나무를 자를 땐 거대한 전기톱(아우터 조인)을 가져와야 합니다.
Ⅴ. 기대효과 및 결론
정량/정성 기대효과
| 구분 | 스칼라 캐싱 미활용 조인 | 코드성 스칼라 캐싱 및 언네스팅 활용 | 개선 효과 |
|---|---|---|---|
| 정량 | 공통코드 5개 조인 시 5방향 옵티마이저 혼란 | 스칼라 5개로 Driving 테이블 직관적 고정 | 쿼리 플랜 최적화 성공률 증가 및 I/O 블록 30% 감소 |
| 정량 | 회원별 이력 스칼라 N번 반복 스캔 | 인라인 뷰 집합(Outer Join)으로 언네스팅 | 무거운 스칼라 반복 연산을 단 1회의 통 스캔(Hash Join)으로 99% 속도 단축 |
| 정성 | 코드 가독성 파탄 (WHERE 절 지저분함) | SELECT 절 안에 서브쿼리 함수형 캡슐화 | 쿼리 작성 직관성 및 프론트엔드 연동 데이터 직관적 맵핑 |
미래 전망
- 스칼라 서브쿼리의 종말과 CTE(WITH 절)의 지배: 쿼리 튜너(DBA)들은 SELECT 절에 괄호가 주렁주렁 매달린 것을 극도로 혐오한다. 최근의 개발 패러다임은 이 스칼라 로직들을 쿼리 맨 위에
WITH절로 모조리 뽑아내어 미리 가상 테이블로 다 정의해두고, 메인 쿼리에서는 깔끔하게 이름만 가져다 조인시키는 선언적(Declarative) 쿼리 작성법으로 100% 이동했다. 스칼라 쿼리는 점점 레거시 시스템의 잔재로 남아가고 있다. - 메모리 아키텍처의 극대화 (In-Memory DB): SAP HANA나 Redis 같은 인메모리(In-Memory) 기반의 데이터베이스에서는 스칼라 캐싱이고 조인이고 할 것 없이 모든 데이터가 램에 상주하므로 I/O 병목 자체가 소멸했다. 결국 스칼라 쿼리의 무서움이었던 "디스크 I/O 루프" 문제가 하드웨어 인프라의 폭력적인 진화 앞에서 무의미해지는 시대가 오고 있다.
참고 표준
- ANSI SQL:1992: SELECT 절, WHERE 절에서의 단일 행(Scalar) 서브쿼리 사용 문법 최초 표준화
- Oracle Database Optimizer Guide: 스칼라 서브쿼리 캐싱(Scalar Subquery Caching)과 서브쿼리 언네스팅(Subquery Unnesting) 변환 공식 명세
스칼라 서브쿼리는 "데이터를 함수처럼 뽑아내고 싶다"는 절차적 프로그래머들의 강력한 열망과, "데이터는 덩어리(집합)로 다뤄야 한다"는 관계형 데이터베이스(RDBMS)의 철학이 정면으로 충돌하며 만들어낸 가장 기형적이면서도 매력적인 문법이다. 결과물의 한 칸, 한 칸을 채우기 위해 10만 번을 묵묵히 뛰어갔다 돌아오는 그 미련한 동작 원리를 이해하는 자만이, 언제 핀셋(스칼라 캐시)을 꺼내야 할지, 언제 포클레인(아우터 해시 조인)을 꺼내야 할지를 아는 진정한 쿼리 마에스트로로 거듭날 수 있다.
- 📢 섹션 요약 비유: 스칼라 서브쿼리는 은행 VIP 전용 심부름꾼입니다. 단골손님(코드성 데이터) 몇 명의 심부름은 얼굴만 봐도 바로 금고(캐시)에서 돈을 꺼내주어 최고의 효율을 자랑하지만, 매일 10만 명의 새로운 손님(고유값)이 심부름을 시키면 창구를 수십 번 들락거리다 결국 과로로 쓰러져 은행 전체를 마비시키는 무서운 양날의 검입니다.
📌 관련 개념 맵 (Knowledge Graph)
| 개념 명칭 | 관계 및 시너지 설명 |
|---|---|
| Outer Join (아우터 조인) | 스칼라 서브쿼리가 내부적으로 데이터가 없을 때 NULL을 뱉는 성질과 100% 수학적으로 동치(Equivalent)를 이루는 가장 강력한 튜닝 대체재 집합 연산이다. |
| 인라인 뷰 (Inline View) | FROM 절에 들어간 서브쿼리. 스칼라 서브쿼리가 수십만 번 반복되어 DB가 죽어갈 때, 괄호를 FROM 절로 옮겨 한 덩어리로 묶어버리는 해독제로 쓰인다. |
| 옵티마이저 (Query Optimizer) | 인간이 스칼라 서브쿼리로 짜놓은 비효율적인 루프(Loop)를 쳐다보고, "이건 아우터 조인으로 푸는 게 낫겠다"며 몰래 서브쿼리 언네스팅(Unnesting) 마법을 부리는 DB의 뇌다. |
| Scalar Subquery Caching | 오라클 등 DB 엔진이 반복되는 스칼라 입력값을 해시맵(Hash Map) 메모리에 띄워두고 디스크 I/O를 0초로 스킵하게 만들어주는 유일한 생명줄이다. |
| Nested Loop Join (중첩 루프 조인) | 스칼라 서브쿼리가 최악의 경우에 빠졌을 때 작동하는 물리적 연산 원리로, for문 안에 for문을 돌리며 디스크를 무한정 찌르는 가장 멍청한 조인 방식이다. |
👶 어린이를 위한 3줄 비유 설명
- 스칼라 서브쿼리는 밥(메인 쿼리)을 푸다가, 반찬(다른 테이블 데이터)이 딱 하나 필요할 때마다 반찬통 뚜껑을 열어서 **핀셋으로 콩자반 딱 1알(1행 1열)**만 집어오는 방법이에요.
- 반찬 종류가 몇 개 없으면 뚜껑을 한 번만 열고 계속 집어먹으면 되지만(캐싱), 10만 번 넘게 계속 다른 반찬통을 열었다 닫았다 하면 팔(CPU)이 너무 아파서 쓰러져요.
- 그래서 반찬을 너무 많이 꺼내야 할 때는 핀셋(스칼라)을 버리고, 아예 커다란 식판에 반찬을 한 번에 싹 부어놓고 비벼 먹는 방법(조인)으로 바꿔야 밥을 빨리 먹을 수 있답니다!