제프리 울먼의 핵심 아이디어가 현대 데이터베이스에 어떻게 적용되는지: 관계대수, 쿼리 재작성, 비용 기반 최적화, 조인 알고리즘, 컴파일러 스타일의 계획이 시스템 확장에 어떻게 기여하는지 설명합니다.

SQL을 작성하거나 대시보드를 만들거나 느린 쿼리를 튜닝한 사람 대부분은 울먼의 영향 아래에서 일하고 있습니다—그의 이름을 들어본 적이 없더라도요. 울먼은 데이터베이스가 데이터를 어떻게 기술하고, 쿼리를 어떻게 추론하며, 그것을 효율적으로 실행하는지를 정의하는 연구와 교과서를 통해 큰 영향을 미친 컴퓨터 과학자이자 교육자입니다.
데이터베이스 엔진이 당신의 SQL을 빠르게 실행 가능한 형태로 바꿀 때, 그 엔진은 정확하면서도 적응 가능한 아이디어들에 의존합니다. 울먼은 쿼리의 의미를 형식화해 시스템이 안전하게 재작성하도록 도왔고, 데이터베이스 사고와 컴파일러 사고를 연결해 쿼리를 파싱하고 최적화하며 실행 단계로 번역할 수 있게 했습니다.
그 영향력은 눈에 띄는 버튼이나 클라우드 콘솔의 기능으로 나타나지 않습니다. 대신 다음과 같은 형태로 드러납니다:
JOIN을 다시 작성했을 때 쿼리가 빨라짐이 글은 울먼의 핵심 아이디어를 안내로 삼아 실무에서 가장 중요한 데이터베이스 내부 동작을 설명합니다: SQL 밑에 깔린 관계대수, 쿼리 재작성으로 의미 보존하는 방식, 비용 기반 옵티마이저가 선택을 내리는 이유, 그리고 조인 알고리즘이 작업 완료 시간을 좌우하는 방식 등입니다.
또한 파싱, 재작성, 계획 수립 같은 컴파일러적 개념을 일부 끌어올 텐데—데이터베이스 엔진은 많은 사람이 인식하는 것보다 훨씬 정교한 컴파일러처럼 동작합니다.
간단한 약속: 논의는 정확하게 유지하되 수학적 증명은 피하겠습니다. 목표는 다음 번 성능·확장성·혼란스러운 쿼리 행동이 나타났을 때 실무에서 적용할 수 있는 사고 모델을 제공하는 것입니다.
SQL 쿼리를 작성할 때 그것이 “항상 하나의 의미만 가진다”고 기대한다면, 당신은 울먼이 대중화하고 형식화한 아이디어에 의존하고 있는 것입니다: 데이터에 대한 명확한 모델과 쿼리가 무엇을 요구하는지 정확히 기술하는 방식입니다.
관계 모델은 본질적으로 데이터를 테이블(관계)로 취급합니다. 각 테이블에는 행(튜플)과 열(속성)이 있습니다. 지금은 당연해 보이지만 중요한 부분은 이로 인해 만들어지는 규율입니다:
이러한 틀은 핸드웨이빙 없이 정확성과 성능을 추론할 수 있게 합니다. 테이블이 무엇을 나타내는지와 행이 어떻게 식별되는지를 알면, 조인이 무엇을 해야 하는지, 중복이 무엇을 의미하는지, 특정 필터가 왜 결과를 바꾸는지를 예측할 수 있습니다.
울먼의 교육은 종종 관계대수를 작은 쿼리 계산기처럼 사용합니다: 결합할 수 있는 소수의 연산(선택, 투영, 조인, 합집합, 차집합)으로 원하는 것을 표현합니다.
실무에서 중요한 이유: 데이터베이스는 SQL을 대수적 형태로 번역한 뒤 다른 동치 형태로 재작성합니다. 겉보기에 다른 두 쿼리는 대수적으로 동일할 수 있으며—이것이 옵티마이저가 조인 순서를 바꾸거나 필터를 아래로 밀어넣거나 중복 작업을 제거하면서 의미를 보존할 수 있는 방법입니다.
SQL은 주로 "무엇"에 가깝지만, 엔진은 종종 대수적 "어떻게"를 사용해 최적화합니다.
Postgres, Snowflake, MySQL 등 SQL 방언은 다르지만 기초는 변하지 않습니다. 키, 관계, 대수적 등가성을 이해하면 쿼리가 논리적으로 틀렸는지, 단순히 느린지, 그리고 어떤 변경이 플랫폼 간 의미를 보존하는지 판단할 수 있습니다.
관계대수는 SQL의 "수학적 기반"입니다: 원하는 결과를 설명하는 소수의 연산자 집합. 울먼은 이 연산자 관점을 명확하고 가르치기 쉬운 형태로 정리하는 데 기여했으며, 오늘날 대부분의 옵티마이저가 사용하는 사고 모델이기도 합니다.
데이터베이스 쿼리는 몇 가지 빌딩 블록의 파이프라인으로 표현될 수 있습니다:
WHERE)SELECT col1, col2)JOIN ... ON ...)UNION)EXCEPT와 유사)집합이 작기 때문에 정확성에 대해 추론하기 쉬워집니다: 두 대수식이 동치라면 어떤 유효한 데이터베이스 상태에서도 같은 테이블을 반환합니다.
익숙한 쿼리를 보겠습니다:
SELECT c.name
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.total \u003e 100;
개념적으로는:
customers와 orders의 조인에서 시작: customers ⋈ orders
총액이 100을 초과하는 주문만 선택: σ(o.total \u003e 100)(...)
원하는 컬럼 하나만 투영: π(c.name)(...)
모든 엔진이 내부적으로 정확히 같은 표기법을 쓰진 않지만, 핵심 아이디어는 동일합니다: SQL은 연산자 트리가 됩니다.
서로 다른 많은 트리가 같은 결과를 의미할 수 있습니다. 예를 들어, 필터는 종종 더 앞쪽으로 밀어낼 수 있고(조인 전에 적용), 투영은 사용되지 않는 컬럼을 일찍 제거할 수 있습니다. 이런 등가 규칙들이 데이터베이스가 쿼리를 더 저렴한 계획으로 재작성하면서 의미를 바꾸지 않게 해줍니다. 쿼리를 대수로 보면 "최적화"가 마법이 아니라 규칙 기반의 안전한 재형성임을 알게 됩니다.
SQL을 작성하면 데이터베이스가 그 문장을 "쓴 대로" 실행하지 않습니다. SQL은 쿼리 계획으로 번역됩니다: 수행해야 할 작업의 구조적 표현입니다.
좋은 정신 모델은 연산자 트리입니다. 리프는 테이블이나 인덱스를 읽고, 내부 노드는 행을 변환하고 결합합니다. 일반 연산자로는 스캔(scan), 필터(filter/selection), 투영(project), 조인(join), 그룹/집계(group/aggregate), 정렬(sort) 등이 있습니다.
데이터베이스는 일반적으로 계획을 두 계층으로 나눕니다:
울먼의 영향은 의미 보존 변환에 대한 강조에서 드러납니다: 논리 계획을 여러 방식으로 재배열해도 답은 같도록 보장한 뒤 효율적인 물리 전략을 선택합니다.
최종 실행 방식을 선택하기 전에 옵티마이저는 대수적 "클린업" 규칙을 적용합니다. 이 재작성들은 결과를 변경하지 않고 불필요한 작업을 줄입니다.
일반적인 예:
사용자가 한 국가에 속한 주문만 보려는 경우를 보겠습니다:
SELECT o.order_id, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.country = 'CA';
순진한 해석은 모든 users와 모든 orders를 조인한 뒤 캐나다 필터를 적용할 수 있습니다. 의미 보존 재작성은 필터를 아래로 밀어 조인이 적은 행만 건드리게 만듭니다:
country = 'CA'로 필터order_id와 total만 투영플랜 관점에서 옵티마이저는 다음과 같은 형태를
Join(Users, Orders) → Filter(country='CA') → Project(order_id,total)
보다 다음에 가까운 형태로 바꾸려 합니다:
Filter(country='CA') on Users → Join(with Orders) → Project(order_id,total)
같은 답, 적은 작업.
이런 재작성은 당신이 직접 입력하지 않기 때문에 쉽게 간과되지만, 동일한 SQL이 어떤 데이터베이스에서는 빠르고 다른 곳에서는 느린 주된 이유가 됩니다.
쿼리를 실행하면 데이터베이스는 동일한 결과를 내는 여러 방법을 고려한 뒤 가장 "저렴할 것"으로 예상되는 방법을 선택합니다. 이 선택 과정을 비용 기반 최적화라고 하며, 울먼식 이론이 일상 성능에 가장 직접적으로 드러나는 지점입니다.
비용 모델은 옵티마이저가 대체 계획들을 비교할 때 쓰는 채점 시스템입니다. 대부분의 엔진은 몇 가지 핵심 자원을 사용해 비용을 추정합니다:
모델이 완벽할 필요는 없습니다; 자주 방향성으로 맞아떨어져 좋은 계획을 고르면 됩니다.
옵티마이저가 계획을 점수화하기 전에 각 단계에 대해 묻는 질문은: **이 단계에서 얼마나 많은 행이 나올까?**입니다. 이것이 카디널리티 추정입니다.
WHERE country = 'CA'라면 엔진은 테이블의 몇 퍼센트가 일치하는지 추정합니다. 고객과 주문을 조인하면 조인 키에서 몇 쌍이 매칭될지 추정합니다. 이러한 행 수 추정이 인덱스 스캔을 쓸지 전체 스캔을 할지, 해시 조인을 할지 중첩 루프를 할지 등을 결정합니다.
옵티마이저의 추정은 통계에 의해 좌우됩니다: 카운트, 값 분포, 널 비율, 때로는 열 간 상관관계까지요.
통계가 오래되었거나 없으면 엔진은 행 수를 몇 배에서 몇 백 배로 잘못 추정할 수 있습니다. 이때 계획이 종종 실제로는 매우 비싸게 되어 갑작스러운 느려짐, 계획의 무작위적 변경, 조인의 디스크 스필 같은 문제가 발생합니다.
더 나은 추정은 대개 더 많은 작업을 요구합니다: 더 자세한 통계, 샘플링, 더 많은 후보 계획 탐색 등. 그러나 계획 자체도 시간 비용이 있기 때문에 특히 복잡한 쿼리에서는 계획 시간이 문제입니다.
그래서 옵티마이저는 두 목표 사이를 균형을 맞춥니다:
이 트레이드오프를 이해하면 EXPLAIN 출력을 해석할 때 도움이 됩니다: 옵티마이저는 "영리하려고" 하는 것이 아니라 제한된 정보하에서 "예측 가능하게 옳으려"고 합니다.
울먼의 작업은 SQL이 단순히 "실행되는" 것이 아니라 실행 계획으로 번역된다는 강력한 아이디어를 확산시켰습니다. 그중에서도 조인만큼 명백한 곳은 없습니다. 동일한 행을 반환하는 두 쿼리가 엔진이 선택한 조인 알고리즘과 조인 순서에 따라 실행 시간이 천차만별일 수 있습니다.
**중첩 루프 조인(Nested loop join)**은 개념적으로 단순합니다: 왼쪽의 각 행에 대해 오른쪽에서 매칭을 찾습니다. 왼쪽이 작고 오른쪽에 유용한 인덱스가 있을 때 빠릅니다.
**해시 조인(Hash join)**은 한 입력(보통 더 작은 쪽)으로 해시 테이블을 만들고 다른 입력으로 프로브합니다. 정합성(예: A.id = B.id) 조건의 큰 비정렬 입력에 탁월하지만 메모리가 필요하고 스필이 발생하면 이점이 사라집니다.
**병합 조인(Merge join)**은 두 입력을 정렬된 순서로 순회합니다. 양쪽이 이미 정렬되어 있거나 인덱스가 조인 키 순서를 제공할 때 특히 적합합니다.
세 개 이상의 테이블이 있을 때 가능한 조인 순서는 폭발적으로 늘어납니다. 큰 두 테이블을 먼저 조인하면 엄청난 중간 결과가 생성되어 나머지 작업을 느리게 만들 수 있습니다. 더 나은 순서는 보통 가장 선택적(selective)인 필터(가장 적은 행)를 먼저 처리해 중간 결과를 작게 유지하는 방식입니다.
인덱스는 단순히 조회를 빠르게 하는 것뿐만 아니라 특정 조인 전략을 가능하게 합니다. 조인 키에 대한 인덱스는 비용이 큰 중첩 루프를 "행당 시크(seek)" 패턴으로 바꿀 수 있습니다. 반대로 인덱스가 없거나 사용 불가능하면 엔진은 해시 조인이나 큰 정렬을 택할 수밖에 없습니다.
데이터베이스는 단순히 SQL을 "실행"하지 않습니다. SQL을 컴파일합니다. 울먼의 영향은 데이터베이스 이론과 컴파일러 사고 모두에 걸쳐 있으며, 이 연결은 쿼리 엔진이 프로그래밍 언어 도구체인처럼 동작하는 이유를 설명합니다: 번역하고, 재작성하고, 최적화한 뒤 실제 작업을 수행합니다.
쿼리를 전송하면 첫 단계는 컴파일러의 프론트 엔드와 비슷합니다. 엔진은 키워드와 식별자를 토큰화하고 문법을 검사하며 파스 트리(parse tree)(종종 **추상 구문 트리(AST)**로 단순화)를 만듭니다. 여기서 기본적인 오류가 잡힙니다: 누락된 콤마, 모호한 컬럼 이름, 잘못된 그룹화 규칙 등.
유용한 사고 모델: SQL은 "프로그램"인 프로그래밍 언어이고, 그 프로그램은 루프 대신 데이터 관계를 기술합니다.
컴파일러가 문법을 중간 표현(IR)로 변환하듯, 데이터베이스도 SQL 구문을 논리 연산자 집합으로 번역합니다. 예를 들어:
GROUP BY)그 논리 형식은 SQL 텍스트보다 관계대수에 더 가깝고, 의미와 등가성을 추론하기 쉽습니다.
컴파일러 최적화는 프로그램 결과를 동일하게 유지하면서 실행 비용을 줄입니다. 데이터베이스 옵티마이저도 같은 철학을 따릅니다. 사용되는 규칙은 예를 들어:
이는 컴파일러의 "죽은 코드 제거(dead code elimination)"와 같은 철학입니다—기법이 동일하진 않더라도 목표는 의미 보존과 비용 절감입니다.
쿼리가 느리다면 SQL만 보지 마세요. 옵티마이저가 실제로 무엇을 선택했는지 알려주는 쿼리 플랜을 보세요. 플랜은 조인 순서, 인덱스 사용, 시간이 어디에 쓰였는지를 알려줍니다.
실무 팁: EXPLAIN 출력을 성능의 "어셈블리 리스팅"으로 읽는 법을 배우세요. 이 습관은 튜닝을 추측이 아닌 증거 기반 디버깅으로 바꿉니다. 더 자세한 습관은 /blog/practical-query-optimization-habits를 참고하세요.
좋은 쿼리 성능은 종종 SQL을 쓰기 전에 시작됩니다. 울먼의 스키마 설계 이론(특히 정규화)은 데이터베이스가 성장하면서도 데이터를 정확하고 예측 가능하며 효율적으로 유지하도록 구조화하는 방법에 관한 것입니다.
정규화는 다음을 목표로 합니다:
정확성 개선은 나중에 성능 이점으로 이어집니다: 중복 필드가 줄고, 인덱스가 작아지며, 비용이 큰 업데이트가 줄어듭니다.
증명까지 외울 필요는 없습니다. 핵심은:
비정규화는 다음과 같은 경우 합리적입니다:
핵심은 비정규화를 의도적으로 하되, 중복 동기화 프로세스를 마련하는 것입니다.
스키마 설계는 옵티마이저가 무엇을 할 수 있는지를 결정합니다. 명확한 키와 외래키는 더 나은 조인 전략, 안전한 재작성, 더 정확한 행 수 추정을 가능하게 합니다. 반면 과도한 중복은 인덱스를 부풀리고 쓰기 성능을 저하시킬 수 있으며, 다중값 컬럼은 효율적인 프레디케이트를 방해합니다. 데이터 볼륨이 커질수록 초기 모델링 결정이 단일 쿼리의 미세 최적화보다 더 큰 영향을 미칩니다.
시스템이 "확장"될 때는 흔히 더 큰 기계를 추가하는 문제만이 아닙니다. 동일한 쿼리 의미를 보존하면서 엔진이 매우 다른 물리 전략을 선택해 실행 시간을 예측 가능하게 만드는 일이 더 어렵습니다. 울먼이 강조한 형식적 등가성은 그런 전략 변화를 결과를 바꾸지 않고 허용하는 근간입니다.
작은 규모에서는 많은 계획이 "작동"합니다. 스케일 상태에서는 테이블을 스캔할지, 인덱스를 사용할지, 사전 계산된 결과를 사용할지가 초 단위와 시간 차이를 만들 수 있습니다. 이론은 옵티마이저가 결과를 바꾸지 않으면서도 작업을 급격히 바꿀 수 있는 안전한 재작성 규칙(예: 필터 앞당기기, 조인 재배열)을 가져야 한다는 점에서 중요합니다.
파티셔닝(날짜, 고객, 지역 등)은 하나의 논리 테이블을 여러 물리 조각으로 바꿉니다. 이는 계획에 영향을 줍니다:
SQL 텍스트는 동일해도 최적의 계획은 행이 물리적으로 어디에 있는지에 달려 있습니다.
머티리얼라이즈드 뷰는 본질적으로 "저장된 부분 표현"입니다. 엔진이 쿼리가 저장된 결과와 일치하거나 저장된 결과로 재작성될 수 있음을 증명하면 반복적인 조인과 집계를 빠른 조회로 대체할 수 있습니다. 이것이 관계대수 사고가 실제로 작동하는 방식입니다: 등가성을 인식하고 재사용합니다.
캐싱은 반복 읽기를 빠르게 하지만, 너무 많은 데이터를 스캔하거나 거대한 중간 결과를 셔플하거나 큰 조인을 계산해야 하는 쿼리를 구조적으로 개선하지는 못합니다. 스케일 이슈가 나타나면 보통 해결책은: 건드리는 데이터 양을 줄이기(레이아웃/파티셔닝), 반복 계산 줄이기(머티리얼라이즈드 뷰), 또는 계획 변경—단순한 "캐시 추가"가 아닙니다.
울먼의 영향은 간단한 사고 방식으로 나타납니다: 느린 쿼리를 의도(intent)를 표현한 문장으로 보고, 데이터베이스가 그것을 어떻게 재작성했는지 확인하세요. 이익을 얻기 위해 이론가가 될 필요는 없습니다—단지 반복 가능한 루틴이 필요합니다.
보통 실행 시간을 지배하는 부분부터 보세요:
하나만 한다면 행 수가 처음으로 폭발하는 연산을 찾아내세요. 보통 그 지점이 근본 원인입니다.
쓰기 쉽고 비용이 큰 패턴들:
WHERE LOWER(email) = ...는 인덱스 사용을 막을 수 있습니다(대신 정규화된 컬럼이나 함수 기반 인덱스 사용).관계대수 사고는 두 가지 실용적 움직임을 장려합니다:
WHERE 조건을 적용해 입력을 축소하세요.좋은 가설 예: “이 조인이 비싼 이유는 우리가 너무 많은 행을 조인하기 때문입니다; orders를 최근 30일로 먼저 필터하면 조인 입력이 줄어듭니다.”
간단한 의사결정 규칙:
EXPLAIN이 피할 수 있는 작업(불필요한 조인, 늦은 필터링, non-sargable 프레디케이트)을 보여준다면 재작성하세요.목표는 "영리한 SQL"이 아니라 예측 가능하고 작은 중간 결과를 만드는 것입니다—바로 울먼의 아이디어가 등가성 보존을 통해 찾아주게 되는 개선들입니다.
이 개념들은 DBA 전용이 아닙니다. 애플리케이션을 배포한다면 스키마 형태, 키 선택, 쿼리 패턴, 데이터 접근 계층 등으로 이미 데이터베이스와 쿼리 계획 결정에 영향을 주고 있습니다.
예를 들어, vibe-coding 워크플로(예: 채팅 인터페이스로 React + Go + PostgreSQL 앱을 생성하는 Koder.ai)를 사용한다면, 울먼식 사고 모델은 실무에서 안전망 역할을 합니다: 생성된 스키마를 키와 관계가 깔끔한지 검토하고, 앱이 의존하는 쿼리를 점검하며, 문제 발생 전에 EXPLAIN으로 성능을 검증할 수 있습니다. “쿼리 의도 → 플랜 → 수정”의 반복을 빠르게 할수록 가속화된 개발에서 더 많은 가치를 얻을 수 있습니다.
이론을 별도의 취미로 공부할 필요는 없습니다. 울먼식 기초에서 빠르게 이득을 얻는 가장 빠른 방법은 쿼리 플랜을 자신 있게 읽을 수 있을 만큼만 배우고, 실제 데이터베이스에서 연습하는 것입니다.
다음 책과 강의 주제를 찾아보세요(제휴 없음—널리 인용되는 출발점입니다):
관찰 가능한 것에 묶어두고 작은 단계로 시작하세요:
실제 쿼리 2–3개를 골라 반복하세요:
IN을 EXISTS로 바꾸기, 프레디케이트를 더 앞에 밀기, 불필요한 컬럼 제거 후 결과 비교플랜 기반 언어를 사용하세요:
이것이 울먼 기초의 실용적 대가입니다: 추측 없이 성능을 설명할 수 있는 공통 언어를 얻는 것입니다.
제프리 울먼은 데이터베이스가 쿼리의 의미를 어떻게 표현하고, 안전하게 동치 변환을 통해 더 빠르게 실행할 수 있는지를 정형화한 연구자이자 교육자입니다. 그의 기초 이론은 엔진이 쿼리를 재작성하거나 조인 순서를 바꾸거나 다른 실행 계획을 선택하면서도 동일한 결과 집합을 보장할 때마다 적용됩니다.
관계대수는 선택(select), 투영(project), 조인(join), 합집합(union), 차집합(difference) 같은 소수의 연산으로 쿼리 결과를 정확히 기술하는 연산 집합입니다. 데이터베이스 엔진은 보통 SQL을 이런 대수 연산 트리로 번역해 등가 규칙(예: 필터를 일찍 적용)을 적용한 뒤 실행 전략을 선택합니다.
재작성은 항상 같은 결과를 반환한다는 것을 증명할 수 있어야 실용적입니다. 등가 규칙을 통해 옵티마이저는 다음과 같은 작업을 할 수 있습니다:
WHERE 필터를 조인 전에 밀어넣기이런 변화는 의미를 바꾸지 않으면서 작업량을 크게 줄일 수 있습니다.
논리 계획은 어떤 연산을 해야 하는지(필터, 조인, 집계 등)를 추상적으로 나타냅니다. 물리 계획은 그 연산을 실제로 어떻게 실행할지(인덱스 스캔 vs 전체 스캔, 해시 조인 vs 중첩 루프, 병렬화 등)를 정합니다. 대부분의 성능 차이는 물리적 선택에서 발생하며, 이 선택은 논리적 재작성으로 가능해집니다.
비용 기반 최적화는 여러 유효한 계획을 비교해 예상 비용이 가장 낮은 계획을 고르는 과정입니다. 비용은 보통 처리되는 행 수, I/O(디스크/SSD 페이지 읽기 및 캐시 효과), CPU(필터링·해싱·정렬·집계), 메모리(오퍼레이션이 RAM에 맞는지 여부) 같은 현실적 요소로 추정됩니다.
카디널리티 추정은 각 단계가 얼마나 많은 행을 생성할지 옵티마이저가 예측하는 것입니다. 이 추정은 조인 순서, 조인 유형, 인덱스 스캔 여부 등을 결정합니다. 통계가 오래되었거나 없으면 추정이 크게 빗나가고, 그 결과 갑작스러운 느려짐이나 디스크로의 스필, 예기치 않은 계획 변경이 발생합니다.
다음 신호들에 주목하세요:
플랜을 컴파일된 코드의 어셈블리처럼 보세요: 엔진이 실제로 무엇을 선택했는지 보여줍니다.
정규화는 사실의 중복과 갱신 이상(anomaly)을 줄여 테이블과 인덱스를 더 작게 유지하게 하고, 조인을 더 신뢰성 있게 만듭니다. 분석용으로 읽기 위주인 워크로드에서는 의도적으로 중복을 허용하는 비정규화가 타당할 수 있지만, 이는 명확한 동기화/갱신 규칙을 동반해야 합니다.
스케일 환경에서는 같은 쿼리 의미를 유지하면서 물리적 전략을 바꾸는 일이 핵심입니다. 자주 쓰이는 수단은:
캐싱은 반복 읽기에 유용하지만, 너무 많은 데이터를 건드리거나 큰 중간 조인을 처리해야 하는 쿼리를 구조적으로 개선하지는 못합니다.